使用类型擦除来欺骗集合
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
)?
(由于问题的变化而更新。)
你的最后一行并没有抛出异常,只是因为 ArrayList
的 toString()
实现根本不关心元素类型。简单地说,它递归调用存储在其内部 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)
,这将失败。
(这是堆污染是一个严重错误的原因之一;失败可能发生在远离实际错误代码的地方。)
我试图说明类型擦除以向某人解释它,但我发现了一些意想不到的事情。这是我试过的片段:
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
)?
(由于问题的变化而更新。)
你的最后一行并没有抛出异常,只是因为 ArrayList
的 toString()
实现根本不关心元素类型。简单地说,它递归调用存储在其内部 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)
,这将失败。
(这是堆污染是一个严重错误的原因之一;失败可能发生在远离实际错误代码的地方。)