通配符与泛型方法
以下打印范围内所有元素的方法之间是否有任何实际差异?
public static void printA(Iterable<?> range)
{
for (Object o : range)
{
System.out.println(o);
}
}
public static <T> void printB(Iterable<T> range)
{
for (T x : range)
{
System.out.println(x);
}
}
显然,printB
涉及到对 Object 的额外检查强制转换(参见第 16 行),这对我来说似乎相当愚蠢 —— 不是所有东西都是 Object 吗?
public static void printA(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 24
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: astore_1
17: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
24: aload_2
25: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
30: ifne 10
33: return
public static void printB(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 27
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: checkcast #3; //class java/lang/Object
19: astore_1
20: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: aload_2
28: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
33: ifne 10
36: return
Is there any practical difference between the following approaches to print all elements in a range?
public static void printA(Iterable<?> range)
{
for (Object o : range)
{
System.out.println(o);
}
}
public static <T> void printB(Iterable<T> range)
{
for (T x : range)
{
System.out.println(x);
}
}
Apparently, printB
involves an additional checked cast to Object (see line 16), which seems rather stupid to me -- isn't everything an Object anyway?
public static void printA(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 24
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: astore_1
17: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
24: aload_2
25: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
30: ifne 10
33: return
public static void printB(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 27
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: checkcast #3; //class java/lang/Object
19: astore_1
20: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: aload_2
28: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
33: ifne 10
36: return
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在您的示例中,泛型类型仅在签名的一个位置使用。在这种情况下,对于调用者来说,类型
T
与通配符相比没有任何优势。在您的示例中,该类型对于方法的实现者也没有任何优势。我发现通配符版本对于调用者来说更容易理解,因为它明确表示“我根本不关心类型”。
在您的示例中,
checkcast
确实是多余的。如果T
有界,则需要它,就像在T extends Number
中一样。然后需要对Number
进行检查,因为局部变量x
将是Number
类型,但Iterator.next()
方法仍然返回Object
。看起来 Java 编译器并没有费心去优化强制转换。 JIT 可能会在运行时执行此操作。更新:
如果泛型类型在多个地方使用,就像 cletus 的答案一样,您别无选择,只能使用泛型类型
T
,否则编译器看不到任何联系参数类型/返回类型之间(任何两个通配符对于编译器来说都是不同的)。边界情况是签名仅在一个位置具有类型,但实现需要它是通用类型而不是通配符。考虑一个
void swap(Listlist, int a, int b)
方法。它需要从列表中取出元素并将它们放回列表中。IIRC,Effective Java 建议使用带有通配符的公共方法,以及带有包含实际实现的类型的内部辅助方法。这样,用户获得了一个简单的 API,而实现者仍然具有类型安全性。In your example, the generic type is used in exactly one spot of the signature. In this scenario, a type
T
has no advantage over a wildcard for the caller. In your example, the type also has no advantage for the implementor of the method.I find the wildcard version easier to understand for a caller, since it explicitly says "I don't care about the type at all".
In your example, the
checkcast
is indeed superfluous. It would be required ifT
was bounded, like inT extends Number
. Then a checkcast forNumber
is required, because the local variablex
will be of typeNumber
, but theIterator.next()
method still returnsObject
. Seems like the Java compiler does not bother optimizing away the cast. The JIT probably will do so at runtime.UPDATE:
If the generic type is used in several spots, like in cletus's answer, you have no option but to use a generic type
T
, or else the compiler sees no connection between the parameter type/return type (any two wildcards are distinct for the compiler).A borderline case is when the signature only has the type in one spot, but the implementation needs it to be a generic type rather than a wildcard. Think of a
void swap(List<T> list, int a, int b)
method. It needs to take elements out of the list and put them back in. IIRC, Effective Java suggests using a public method with the wildcard, and an internal helper method with a type containing the actual implementation. This way, the user gets a simple API, and the implementer still has type safety.第二个更灵活。一个更好的例子是:它说了一些关于类型的事情。这对您是否有用取决于该函数的作用。
第二个显示了当您想从方法返回某些内容时它的有用性:
复制数组可能是一个更好的示例,但要点仍然是您不能真正使用
List
执行上述操作。The second is more flexible. A better example is: It's saying something about the type. Whether or not that's useful to you depends on what the function does.
The second shows its usefulness when you want to return something from the method:
It's possibly a better example to copy the array but the point remains that you can't really do the above with
List<?>
.并不真地。生成的字节码实际上应该是相同的。
Not really. The resulting bytecode should be virtually identical.