代码之家  ›  专栏  ›  技术社区  ›  fredoverflow

通配符与泛型方法

  •  13
  • fredoverflow  · 技术社区  · 15 年前

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

    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 包括一个额外的选中的对象强制转换(见第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 回复  |  直到 15 年前
        1
  •  7
  •   Christian Semrau Louis Wasserman    15 年前

    在您的示例中,使用了泛型类型 就在一个地方 签名。在此方案中,类型 T 与通配符相比没有优势 对于呼叫者 . 在您的示例中,类型也没有优势 对于实施者 的方法。

    我发现通配符版本对于调用者来说更容易理解,因为它明确表示 “我一点也不在乎那种类型” .

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

    更新:

    如果在几个地方使用泛型类型,比如在克莱图斯的答案中,那么除了使用泛型类型之外,您没有其他选择。 T 或者编译器在参数类型/返回类型之间看不到任何连接(对于编译器,任何两个通配符都是不同的)。

    边界条件是签名只有一个点的类型,但实现需要它是通用类型而不是通配符。想到一个 void swap(List<T> list, int a, int b) 方法。它需要将元素从列表中取出并放回列表中。IIRC 通用程序设计 建议使用带有通配符的公共方法,以及包含实际实现的类型的内部助手方法。这样,用户就得到了一个简单的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
  •  2
  •   cletus    15 年前

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

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

    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<?> .

        3
  •  0
  •   Marcelo Cantos    15 年前

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