使用类型擦除来欺骗集合

Tricking a collection with type erasure

我试图说明类型擦除以向某人解释它,但我发现了一些意想不到的事情。这是我试过的片段:

Object o = new ArrayList<Integer>();
List<String> list = (List<String>) o;
list.add("Oups");
System.out.println(list);

我原以为这会在最后一行抛出一个 ClassCastException(或类似的东西),但它没有。我还尝试在转换为 Object 之前用整数值填充 ArrayList,结果是一样的。

为什么这与

不同
Integer[] arr0 = { 1,2,3,4 };
Object[] arr1 = arr0;
arr1[3] = "hey";

哪个抛出异常(ArrayStoreException)?

(由于问题的变化而更新。)

你的最后一行并没有抛出异常,只是因为 ArrayListtoString() 实现根本不关心元素类型。简单地说,它递归调用存储在其内部 Object[] 元素数组中的每个对象的 toString(),因此永远不需要检查其元素的 运行time 类型。

但是,这段代码实际上也不会失败:

List<Integer> il = new ArrayList<Integer>();
Object o = il;
List<String> sl = (List<String>)o;
sl.add("Oups");
System.out.println(il.get(0));

这是因为 System.out.println 的参数类型是 Object,因此编译器在 get() 处优化了对 Integer 的隐式转换,因为它不是严格意义上的 "necessary"。有人可能会或可能不会争辩说这对编译器不利,因为早期转换会更早地检测到错误,但事实就是如此。

另一方面,用这个 替换最后一行会 抛出异常:

System.out.println(il.get(0) + 5);

因为现在您要执行的操作假定元素是 Integer,并且转换包含在生成的代码中。

另一方面,原始数组对它们的元素类型有 运行 时间的了解,并且会在每次插入时断言它。

因为在 运行 时进行了类型擦除,所以无论如何它都是 Object 类型,而你的 List 是空的,你永远不会迭代它

List<Integer> o = new ArrayList<Integer>();
List<String> list = (List<String>) (Object) o;
list.add("Oups");
for (Integer i : o) { // <-- Iterate the List<Integer>
    System.out.println(i);
}

将抛出(在 运行 时)

Exception in thread "main" java.lang.ClassCastException: 
    java.lang.String cannot be cast to java.lang.Integer

ArrayList 中的数组是 Object[],而不是 Integer,因此向其分配任意对象都没有问题。当您尝试执行 list.get(0) 时它会中断,因为编译器将插入一个隐式转换 (Integer) list.get(0),这将失败。

(这是堆污染是一个严重错误的原因之一;失败可能发生在远离实际错误代码的地方。)