通配符与泛型方法

发布于 2024-08-30 16:22:52 字数 1788 浏览 4 评论 0原文

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

小…楫夜泊 2024-09-06 16:22:52

在您的示例中,泛型类型仅在签名的一个位置使用。在这种情况下,对于调用者来说,类型 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,而实现者仍然具有类型安全性。

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

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 if T was bounded, like in T extends Number. Then a checkcast for Number is required, because the local variable x will be of type Number, but the Iterator.next() method still returns Object. 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.

public void swap(List<?> list, int a, int b){
    swapHelper(list, a, b);
}
private <T> void swapHelper(List<T> list, int a, int b){
    ...
}
妄想挽回 2024-09-06 16:22:52

第二个更灵活。一个更好的例子是:它说了一些关于类型的事情。这对您是否有用取决于该函数的作用。

第二个显示了当您想从方法返回某些内容时它的有用性:

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 执行上述操作。

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:

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

It's possibly a better example to copy the array but the point remains that you can't really do the above with List<?>.

乖乖 2024-09-06 16:22:52

并不真地。生成的字节码实际上应该是相同的。

Not really. The resulting bytecode should be virtually identical.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文