通配符与通用方法

时间:2010-05-02 09:36:04

标签: java generics wildcard

以下打印范围内所有元素的方法之间是否存在实际差异?

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行),这对我来说似乎相当愚蠢 - 不是所有的对象都不是吗?

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

3 个答案:

答案 0 :(得分:7)

在您的示例中,泛型类型在签名的正好一个中使用。在这种情况下,类型T与调用者的通配符相比没有优势。在您的示例中,该类型对于方法的实现者也没有的优势。

我发现调用者更容易理解通配符版本,因为它明确地说“我根本不关心类型”

在您的示例中,checkcast确实是多余的。如果T有限,则需要它,例如T extends Number。然后需要Number的校验广播,因为局部变量x的类型为Number,但Iterator.next()方法仍会返回Object。似乎Java编译器不打算优化演员表。 JIT可能会在运行时这样做。

<强>更新

如果在几个地方使用泛型类型,比如在cletus的答案中,你别无选择,只能使用泛型类型T,否则编译器看不到参数类型/返回类型之间没有连接(任何两个通配符对于编译器来说是不同的。)

边界情况是签名仅在一个位置具有类型,但实现需要它是通用类型而不是通配符。想一下void swap(List<T> list, int a, int b)方法。它需要从列表中取出元素并将它们重新放入.IIRC, Effective Java 建议使用带有通配符的公共方法,以及带有包含实际实现的类型的内部帮助器方法。这样,用户就可以获得一个简单的API,并且实现者仍然具有类型安全性。

public void swap(List<?> list, int a, int b){
    swapHelper(list, a, b);
}
private <T> void swapHelper(List<T> list, int a, int b){
    ...
}

答案 1 :(得分:2)

第二种更灵活。一个更好的例子是:它说的是类型。这对你有用是否取决于函数的作用。

当你想从方法中返回一些东西时,第二个显示它的用处:

public static <T> List<T> reverse(List<T> list) {
  for (int i=0; i<n/2; i++) {
    T value = list.get(i);
    list.set(i, list.get(list.size() - i - 1));
    list.set(list.size() - i = 1, value);
  }
  return list;
}

这可能是复制数组的一个更好的例子,但关键是你无法用List<?>完成上述操作。

答案 2 :(得分:0)

不是真的。生成的字节码应该几乎相同。