Why can a Double be added to a List of Integers using reflection

Java

Java Problem Overview


Why does this code run without any exceptions?

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));
}

Java Solutions


Solution 1 - Java

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);

Solution 2 - Java

Because of the type erasure - there are no runtime checks for the generics, during compilation type parameters are removed: https://stackoverflow.com/questions/339699/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));

Solution 3 - Java

The reason for this is type erasure: the fact that this is a list of Integers 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.

Solution 4 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdmond WangView Question on Stackoverflow
Solution 1 - JavaJB NizetView Answer on Stackoverflow
Solution 2 - JavaAndrey ChaschevView Answer on Stackoverflow
Solution 3 - JavaSergey KalinichenkoView Answer on Stackoverflow
Solution 4 - JavaNazgulView Answer on Stackoverflow