为什么可以使用reflection将Double添加到整数列表中

为什么这个代码运行没有任何例外?

public static void main(String args[]) { List<Integer> a = new ArrayList<Integer>(); try { a.getClass() .getMethod("add", Object.class) .invoke(a, new Double(0.55555)); } catch (Exception e) { e.printStackTrace(); } System.out.println(a.get(0)); } 

generics是编译时的事情。 在运行时,使用常规的ArrayList ,没有任何额外的检查。 由于您正在通过使用reflection将元素添加到列表中来绕过安全性检查,所以没有任何东西可以防止将Double存储在List<Integer> 。 就像你做的一样

 List<Integer> list = new ArrayList<Integer>(); List rawList = list; rawList.add(new Double(2.5)); 

如果你希望你的列表在运行时实现types检查,那就使用

 List<Integer> checkedList = Collections.checkedList(list, Integer.class); 

由于types擦除 – 没有对generics的运行时检查,在编译期间,types参数被删除: Javagenerics – types擦除 – 何时发生 。

您可能会感到惊讶,但您不需要使用reflection来将Double添加到List<Integer>

 List<Integer> a = new ArrayList<Integer>(); ((List)a).add(new Double(0.555)); 

其原因是types擦除 :这是一个Integer列表的事实是编译器已知的,而不是JVM。

一旦代码被编译, List<Integer>变成List<Object> ,允许基于reflection的代码完整无误。

请注意,你自己的代码有一个很强的暗示为什么这个工程的原因:

 a.getClass() .getMethod("add", Object.class) // <<== Here: Object.class, not Integer.class .invoke(a, new Double(0.55555)); 

另外请注意,通过一些创造性的使用而不是reflection,你可以达到同样的恶果。 所有这些都是devise决定使用types擦除来实现Javagenerics的结果。

generics只是java提供的编译时工具。 在generics之前,没有办法在编译时确保从集合中获得的“对象”实例实际上是您所期望的types。 我们必须将对象转换为适当的types才能在代码中使用,这可能是有风险的,因为只有在运行时JVM才会抱怨ClassCastException 。 在编译时没有任何东西可以保护我们免于此。

generics在编译时通过在集合中强制执行types检查来解决这个问题。 但是generics的另一个重要的事情是它们在运行时不存在。 如果你反编译一个包含像List或Map这样的types集合的类,并且看到从它生成的java源代码,那么你就不会在那里find你的generics集合声明。 由于reflection代码在运行时工作,并且没有编译时间,所以你不会在那里得到exception。 尝试在编译时使用正常的放置或添加操作来做同样的事情,你会得到一个编译时错误。