当传递给使用varargs的第二个泛型方法时,我在泛型方法中丢失了变量的类型

时间:2012-07-16 09:27:19

标签: java generics

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

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()callPrintClass(),似乎一切正常。 callPrintClass()采用泛型参数并将其传递。 printClass()通过正确的类型识别此变量,而不是关心谁发送参数,然后按照预期执行并打印java.lang.String

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

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

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

2 个答案:

答案 0 :(得分: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});
}

What's the reason I can't create generic array types in Java? - 这个问题涉及为什么不可能创建泛型类型的数组,也许同样解释了这个问题。

答案 1 :(得分:2)

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

在Java中,您无法创建Non-reifiable types的数组(类型信息因擦除而丢失的类型)。因此,printClassVarargs(T ... str)等通用变量将转换为printClassVarargs(Object[] str),从而有效地删除了类型信息。这是您在测试中观察到的内容。

---编辑---

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

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

}

观察主#12,创建字符串obj的新String []以用作printClassVarargs()callPrintClassVarargs()的参数

在主#19 printClassVarargs上调用,创建的String []作为参数。这导致printClassVarargs在运行时知道该对象的类型。这种类型得以保留。

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

正如您所看到的,当传递给callPrintClassVarargs(T str)的泛型参数时,参数的类型会被删除。

QED