4

我想最好从我正在查看的行为开始:

public class genericTest {
    public static void main(String[] args) {
        String str = "5318008";

        printClass(str);              // class java.lang.String
        callPrintClass(str);          // class java.lang.String

        printClassVarargs(str);       // class java.lang.String
        callPrintClassVarargs(str);   // class java.lang.Object
    }

    public static <T> void printClass(T str) {
        System.out.println(str.getClass());
    }

    public static <T> void printClassVarargs(T ... str) {
        System.out.println(str.getClass().getComponentType());
    }

    public static <T> void callPrintClass(T str) {
        printClass(str);
    }

    @SuppressWarnings("unchecked")
    public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(str);
    }
}

看着printClass()and callPrintClass(),似乎一切正常。callPrintClass()接受一个通用参数并传递它。printClass()通过其正确的类型识别此变量,而不关心谁在发送参数,然后按预期执行并打印java.lang.String.

但是当我们尝试使用可变参数时,这将停止工作。我希望printClassVarargs()认识到它的参数是 type String[],就像没有 varargs 的方法识别其参数的类型一样。另请注意,如果我直接调用,则不会发生这种情况printClassVarargs()(很高兴在那里输出String),但只有当它被调用时callPrintClassVarargs(),它才会忘记其参数的类型并假定它得到一个Object. 我也意识到我必须在这里抑制编译器警告,这通常在我尝​​试转换泛型时出现,但我不确定那里到底发生了什么。

所以我的问题真的是两个。这种行为背后的原因是什么?这是类型擦除的某种结果,还是 Java 处理数组的方式?其次,有没有办法解决这个问题?

当然,这只是一个简单的例子。我不是试图以这种方式打印类名,而是最初在编写重载方法来连接数组时发现了问题。

4

2 回答 2

2

我认为问题归结为无法创建泛型类型的数组。如果callPrintClassVarargs修改为显式创建一个新的数组实例并将其传递给,printClassVarargs则底层问题变得显式。

// doesn't work, gives compiler error (cannot create array of generic type)
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new T[]{str});
}

//This works
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new Object[]{str});
}

我无法在 Java 中创建泛型数组类型的原因是什么?- 这个问题涉及为什么不能创建泛型类型的数组,也许同样可以解释这个问题。

于 2012-07-16T09:50:38.043 回答
2

Varargs 是由编译器转换为给定类型数组的语法糖。这意味着 amethod(Type arg...)将变为method(Type[] arg)

在 Java 中,您不能创建不可具体化类型的数组(类型信息因擦除而丢失的类型)。因此,一个通用的可变参数如printClassVarargs(T ... str)将转换为printClassVarargs(Object[] str),有效地导致类型信息的擦除。这是您在测试中观察到的。

- - 编辑 - -

要回答您关于 和 之间区别的问题(cfr 评论)printClassVarargs(str)callPrintClassVarargs(str)我们可以查看您的测试类的字节码以获取所需的线索:

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc #16; //String 5318008
   2:   astore_1
   3:   aload_1
   4:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   7:   aload_1
   8:   invokestatic    #22; //Method callPrintClass:(Ljava/lang/Object;)V
   11:  iconst_1
   12:  anewarray   #25; //class java/lang/String
   15:  dup
   16:  iconst_0
   17:  aload_1
   18:  aastore
   19:  invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   22:  aload_1
   23:  invokestatic    #31; //Method callPrintClassVarargs:(Ljava/lang/Object;)V
   26:  return

public static void printClass(java.lang.Object);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   10:  return

public static void printClassVarargs(java.lang.Object[]);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #59; //Method java/lang/Class.getComponentType:()Ljava/lang/Class;
   10:  invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   13:  return

public static void callPrintClass(java.lang.Object);
  Code:
   0:   aload_0
   1:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   4:   return

public static void callPrintClassVarargs(java.lang.Object);
  Code:
   0:   iconst_1
   1:   anewarray   #3; //class java/lang/Object
   4:   dup
   5:   iconst_0
   6:   aload_0
   7:   aastore
   8:   invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   11:  return

}

printClassVarargs()在 main#12 上观察你的字符串 obj的一个新 String[] 被创建用作参数callPrintClassVarargs()

在 main#19printClassVarargs被调用,创建的 String[] 作为参数。这导致在printClassVarargs运行时知道该对象的类型。这种类型被保留了下来。

在 main#23 callPrintClassVarargs上调用,也使用创建的 String[] 作为参数。然后在callPrintClassVarargs#1一个新数组上创建。这一次,泛型类型声明中没有可用的类型信息,因此创建了一个新的 Object[]。String[] 存储在这个数组中并callPrintClassVarargs#8传递给printClassVarargs,它现在必须在一个 Object[] 上工作,它的 componentType 是 object。

正如您所观察到的,当您的参数类型传递给callPrintClassVarargs(T str).

量子点

于 2012-07-16T10:07:41.590 回答