通配符与泛型方法

2022-09-04 22:29:00

以下打印范围内所有元素的方法之间是否有任何实际区别?

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);
    }
}

显然,它涉及对 Object 的额外检查转换(参见第 16 行),这对我来说似乎相当愚蠢 - 无论如何,一切都不是一个对象吗?printB

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

答案 1

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

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

在你的例子中,确实是多余的。如果 是有界的,则需要它,如 在 中一样。然后需要一个 checkcast,因为局部变量的类型为 ,但该方法仍返回 。似乎Java编译器不会费心优化转换。JIT 可能会在运行时执行此操作。checkcastTT extends NumberNumberxNumberIterator.next()Object

更新:

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

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

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

答案 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<?>


推荐