为什么在Java中使用数组擦除泛型?

时间:2015-07-10 07:54:16

标签: java arrays generics

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

但为什么不例外呢?为什么不留下数组的泛型类型信息(仅适用于它们)?

这背后的设计决定是什么?在我看来,它会让生活更轻松,有可能做到这一点。

T[] genericArray = new T[10];

2 个答案:

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

打印:

Object

因此,我们可以看到,当它被编译时,它被编译为分别适用于NumberArrayList<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>转换为String[] - 如果ArrayList实际将其内容存储在数组中,那么这是不可能的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

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

20

正如您在ArrayList.get行上看到的那样,String的结果实际上是投放到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[]

将为false,因为第一个数组将具有类String[],第二个数组将具有类Object[]

当然,可以更改Java,以便所有数组都是ArrayList<String>类型,并且所有访问都将使用强制转换,就像使用泛型时一样。但这会破坏向后兼容性,而使用泛型并不是因为ArrayList$headers = array('Content-type: multipart/form-data','Authorization: '.$api_key,); $curl_post_data = array('employee_id' => $employee_id,'files' => $_FILES); $curl = curl_init('http://[...mydomain...]/v1/uploadEquipmentDocument'); curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $curl_post_data); curl_setopt($curl, CURLOPT_USERPWD, $api_key); $curl_response = curl_exec($curl); 具有相同的类。

答案 1 :(得分:1)

new T[10];这样的构造的问题是T可能是任何东西,包括Void(实际上是一个不可实例化的类,会产生编译错误)。

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

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