2015-07-10 79 views
1

我知道泛型类型信息在Java编译时被删除,因此无法创建泛型类型的数组(因为在运行时无法有效地强制插入到数组中)。

但为什么不能例外?为什么不留下数组的通用类型信息(并且仅限于它们)?

这背后的设计决定是什么?在我看来,它会使生活更轻松,这将有可能做到这一点。为什么在Java中的数组中删除泛型?

T[] genericArray = new T[10]; 
+0

可能向后兼容? – Codebender

+0

这可能是重复的。 – Raedwald

+0

@BendeguzNagy我不知道在T [] genericArray = new T [10];'vs使用Object [] genericArray = new Object [10]'时,你会得到什么好处? type参数几乎没有用处(除非你在向'T [] genericArray'添加一个元素时期待,编译器应该推导出这个类型并且不允许添加那些不是插入第一个元素类型的元素?当然,这是不可能的,而且要求太多) – CKing

回答

4

简短的回答:

这是因为仿制药的元数据来帮助编译器帮助您捕捉类型 错误,一切都被编译使用最小公分母 (通常为Object)和类型转换。这不是用数组完成的,因为数组 是它们自己的类。即一个ArrayList<String>ArrayList<Number> 都具有类ArrayList,但String一个阵列具有String[]类和 Number阵列具有Number[]类。

龙答:正在使用泛型将使用至少 公分母(这通常是Object

在编译时,应有尽有。这表现在 下面的代码:

public class Generics { 

    public static <T> void print(T what) { 
     System.out.println(what); 
    } 

    public static <T extends Number> void printNumber(T what) { 
     System.out.println(what); 
    } 

    public static void main(String[] args) { 
     Arrays.stream(Generics.class.getDeclaredMethods()) 
       .filter(m -> m.getName().startsWith("print")) 
       .forEach(Generics::print); 
    } 

} 

此打印:

public static void Generics.print(java.lang.Object) 
public static void Generics.printNumber(java.lang.Number) 

所以我们可以看到,当它的编译它被编译为分别ObjectNumber工作方法。

这是这样的原因,将编译并运行:

ArrayList<String> list = new ArrayList<>(); 
list.add("foo"); 
ArrayList<Object> list2 = (ArrayList<Object>)(Object)list; 
list2.add(Integer.valueOf(10)); 
System.out.println(list2.get(0)); 
System.out.println(list2.get(1)); 

如果你尝试,你会看到它打印

foo 
10 

所以由下/上投我们把我们的ArrayList<String>变成了ArrayList<Object>--如果ArrayList实际上将它的内容存储在类型为String[]而不是Object[]的数组中,则这是不可能的。

注意,试图做

System.out.println(list.get(0)); 
System.out.println(list.get(1)); 

将导致ClassCastException。这暗示了 编译器的功能。

请看下面的代码:

public static void doThingsWithList() { 
    ArrayList<String> list = new ArrayList<>(); 
    list.add(""); 
    String s = list.get(0); 
    print(s); 
} 

在编译时,它变成了这个字节码:

public static void doThingsWithList(); 
    Code: 
    0: new   #11     // class java/util/ArrayList 
    3: dup 
    4: invokespecial #12     // Method java/util/ArrayList."<init>":()V 
    7: astore_0 
    8: aload_0 
    9: ldc   #13     // String 
    11: invokevirtual #14     // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 
    14: pop 
    15: aload_0 
    16: iconst_0 
    17: invokevirtual #15     // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 
    20: checkcast  #16     // class java/lang/String 
    23: astore_1 
    24: aload_1 
    25: invokestatic #17     // Method print:(Ljava/lang/Object;)V 
    28: return 

正如你可以从ArrayList.get结果实际上被转换为String线20看到。

所以泛型只是语法糖转化为自动类型转换,并带来额外的好处,即编译器可以使用此语法糖检测在运行时会导致ClassCastException的代码。

现在,为什么编译器不能对String[]Object[]执行相同的操作?不能只是把

public <T> T[] addToNewArrayAndPrint(T item) { 
    T[] array = new T[10]; 
    array[0] = item; 
    System.out.println(array[0]); 
    return array; 
} 

public <T> T[] addToNewArrayAndPrint(T item) { 
    Object[] array = new Object[1]; 
    array[0] = item; 
    System.out.println((T) array[0]); 
    return array; 
} 

没有。因为这将意味着,

Arrays.equals(addToNewArray("foo"), new String[]{ "foo" }); 

是假的,因为第一个数组将有Object[]类和第二将有String[]类。

当然,可以改变Java,以便所有数组的类型为Object[],并且所有访问都将使用强制转换,就像使用泛型一样。但是,这将打破向后兼容性,而使用泛型不会因为ArrayList<String>ArrayList具有相同的类。

+0

简短的回答非常好。 +1 – CKing

1

与结构像new T[10];的问题是,T可以是任何东西,包括Void(这实际上是一个不可级,并会产生一个编译错误)。

如果你仔细想一想,类型是一种语言结构,因此应该在编译时使用它,而不是在运行时。在某些情况下,运行时类型信息和一些语言实际实现它是有意义的,但是您是否需要它并且是“Good Thing™”是值得商榷的。

有关类型擦除一些有用的信息:https://stackoverflow.com/a/21843984/1417546

+2

即使在运行时,“Void”数组也没有问题。它只是另一种类型:'Void [] a = new Void [1];'该类型具有单个成员'null'。 – Lii

+0

当我说不可信时,我提到了你不能实例化一个'Void'对象('new Void()'而不是'new Void []'),'Void'的数组实际上是可以接受的。 – EmirCalabuch

+0

但是我在我的答案中澄清了这个概念,并且我错误地指出它会生成一个运行时错误,并且实际上会产生编译时错误(“构造函数不可见”)。 – EmirCalabuch