本文翻译自《Effective Java(3rd Edition)》Item 28的节选。
数组和泛型类型有两个不同:
- 数组是协变的(covariant),如果 Sub 是 Super 的子类,那么 Sub[] 是 Super[] 的子类;泛型是不变的(invariant),任意两个类型 T1 和 T2,
List<T1>和List<T2>不存在父子关系。因此,数组有更多的风险:
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // 运行时抛异常 ArrayStoreException,编译时有警告但能通过
List<Object> ol = new ArrayList<Long>(); // 编译失败,Incompatible types
- 数组是 reified,它们会在运行时知道并强制元素类型;泛型是 erasure,它们会在编译时强制类型约束,并在运行时擦除元素类型。
Java语言语法中,泛型和数组不能混合使用,new List<E>[]、new List<String>[]、new E[] 都是非法的语句。
为什么泛型数组是非法的?因为它不是类型安全的,如果它是合法的,那么编译时生成的自动类型转换会在运行时抛出 ClassCastException,这违反了泛型类型体系对类型安全的基本保证。
举个例子:
List[] lists = new List[1];
List<Integer> intList = new ArrayList<>();
intList.add(43);
Object[] objects = lists;
objects[0] = intList;
System.out.println(lists[0].get(0));
上面的代码使用了原类型数组,这是合法的,因为 List<Integer> 是 List 的子类,原类型数组的安全隐患是出于原类型本身就存在类型安全隐患(Item 26),虽有隐患但合法。但如果改写成下面的代码片段:
List<String>[] lists = new List<>[1]; // 假设是合法的
List<Integer> intList = new ArrayList<>();
intList.add(43);
Object[] objects = lists;
objects[0] = intList;
System.out.println(lists[0].get(0)); // 编译器类型擦除时,会自动类型转换为String
除了第 1 行外,其它行的代码都是合法的,假设现在允许泛型数组,那么,在上面的第 5 行代码处,将 List<Integer> 实例存储在一个声明为只存储 List<String> 实例元素的数组中,在第 6 行取元素时,编译器会自动将 Integer 元素类型转换为 String,因此在运行时抛出 ClassCastException,这违反了泛型类型体系对类型安全的基本保证。