【问题标题】:Wildcards vs. generic methods通配符与泛型方法
【发布时间】:2010-05-02 09:36:03
【问题描述】:

以下打印范围内所有元素的方法有什么实际区别吗?

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

【问题讨论】:

    标签: java generics wildcard


    【解决方案1】:

    在您的示例中,在签名的一个位置使用了泛型类型。在这种情况下,T 类型对于调用者 没有任何优势。在您的示例中,该类型对于方法的实现者也没有优势

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

    在您的示例中,checkcast 确实是多余的。如果T 是有界的,就像T extends Number 一样,这将是必需的。然后需要检查Number,因为局部变量x 的类型为Number,但Iterator.next() 方法仍返回Object。似乎 Java 编译器不会费心优化演员表。 JIT 可能会在运行时这样做。

    更新:

    如果在多个地方使用泛型类型,例如在 cletus 的回答中,您别无选择,只能使用泛型类型T,否则编译器会发现参数类型/返回类型之间没有任何联系(任何两个通配符对于编译器来说是不同的)。

    边界情况是签名仅在一个位置具有类型,但实现需要它是泛型类型而不是通配符。想想void swap(List&lt;T&gt; 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){
        ...
    }
    

    【讨论】:

    • 我同意:在这种情况下通配符版本更好。
    【解决方案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&lt;?&gt; 做上述事情。

    【讨论】:

    • 我不明白。据我所知,这两种形式可以在完全相同的上下文中使用,并且一种不会比另一种提供更多或更少的类型安全性。我错过了什么吗?
    • @Marcelo 如果您使用 ?版本调用方法将返回一个 List,并且需要将该 List 转换为 List 本身。这有点不安全,因为编译器无法检查实现是否实际生成了 List。合同(带 ?)声明应返回 List,这是真的,即使它不是 List。因此,T 版本将允许编译器检查返回类型,调用方法会更安全。
    • 是的,但是只要 T 只出现在参数列表中的一个位置(如 OP 的示例),它就可以转换为通配符。这个例子不同(T 出现在两个地方,包括返回)。
    • @extraneon:OP 的两个方法都返回 void。 @cletus 似乎在更广泛的背景下讨论这种区别。
    【解决方案3】:

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

    【讨论】:

    • 啊,是的。所以&lt;T&gt; 版本实际上检查内容的类型是否正确,因此如果Iterable&lt;T&gt; 包含不是T 的内容,则可能会失败。我稍微缓和了我的回答。
    • @Marcello:不,它不检查 Iterable 是否包含不是 T 的东西。它只是检查它是否包含不能转换为 Object 的东西。但是泛型是 Java 的实现是如此愚蠢,以至于我认为这只是另一个剩下的......
    • 谢谢 Foxfire。 Java 的泛型从未停止让我惊叹。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-20
    • 1970-01-01
    • 1970-01-01
    • 2014-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多