61

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));
}
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
Edmond Wang
  • 1,687
  • 13
  • 27
  • 3
    What then? What do you expect it to do then? – Lion Nov 30 '13 at 13:33
  • 12
    If you go under the covers and mess about what do you expect to happen. If you find this freaky, there is much more amazing scary things you can do. e.g. you can change `final` fields, the hashCode of an object, change the contents of a String literal, change builtin classes with your own, create new Enum objects, and generally do the sorts of things you are not supposed to do in Java. – Peter Lawrey Nov 30 '13 at 13:51
  • 3
    Please don't downvote legitimate posts unnecessarily. It requires sufficient comment(s) to be left. – Lion Nov 30 '13 at 14:13
  • 2
    For what it's worth, if you were to add `a = Collections.checkedList(a, Integer.class);` after the creation of the List, the code would in fact throw an exception. – VGR Nov 30 '13 at 14:50

4 Answers4

94

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);
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
35

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));
Community
  • 1
  • 1
Andrey Chaschev
  • 16,160
  • 5
  • 51
  • 68
23

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
5

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.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
Nazgul
  • 1,892
  • 1
  • 11
  • 15