Why can a Double be added to a List of Integers using reflection
Generics are a compile-time thing. At runtime, a regular ArrayList
, without any additional check, is used. Since you're bypassing the safety checks by using reflection to add elements to your list, nothing can prevent a Double
from being stored inside your List<Integer>
. Just like if you did
List<Integer> list = new ArrayList<Integer>();
List rawList = list;
rawList.add(new Double(2.5));
If you want your list to implement type checks at runtime, then use
List<Integer> checkedList = Collections.checkedList(list, Integer.class);
Because of the type erasure - there are no runtime checks for the generics, during compilation type parameters are removed: Java generics - type erasure - when and what happens.
You may be surprised, but you don't need to use reflection to add a Double
to a List<Integer>
:
List<Integer> a = new ArrayList<Integer>();
((List)a).add(new Double(0.555));
The reason for this is type erasure: the fact that this is a list of Integer
s is known to the compiler, not to the JVM.
Once the code is compiled, List<Integer>
becomes List<Object>
, allowing the reflection-based code complete with no errors.
Note that your own code has a strong hint at the reason why this works:
a.getClass()
.getMethod("add", Object.class) // <<== Here: Object.class, not Integer.class
.invoke(a, new Double(0.55555));
Also note that you can achieve the same evil result through some creative use of casting, without reflection. All this is a consequence of a design decision to implement Java generics with type erasure.
Generics are only a compile time facility that java provides. Before generics there was no way to make sure at compile time that the 'Object' instance that you get from a collection is actually of the type that you expect. We would have to cast the object to a proper type to make is usable in code and this can be risky as only at runtime time would the JVM complain with a ClassCastException
. There was nothing at compile time to protect us from this.
Generics solved this problem by enforcing type checks in collections at compile time. But another important thing about generics is that they don't exist at runtime. If you decompile a class containing a types collection like List or Map and see the java source generated from it, you would not find your generic collection declaration there. Since the reflections code works at runtime and has no compile time bearing, so you don't get an exception there. Try to do the same at compile time with a normal put or add operation and you would get a compile time error.