8

The result of the following code:

public class ListIntegerDemo1 {

    public static void addToList(List list) {list.add("0067");list.add("bb");}
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        addToList(list);
        System.out.println(list.get(0));
    }     
}

is "0067".

I would have expected a RuntimeException or similar, as I'm adding a String to an Integer List.

Why is it working without any problem?

Radiodef
  • 37,180
  • 14
  • 90
  • 125
ken
  • 471
  • 9
  • 19
  • 1
    See: http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens – Jeroen Vannevel Apr 28 '15 at 13:05
  • in your addToList method, you didn't specify the type your List argument takes, so it's more like `List` and you can add any objects to list – Arkantos Apr 28 '15 at 13:20

2 Answers2

9

At run time, the generic type parameters are erased, so List<Integer> becomes List, and you can add any reference type to it.

If you change addToList(List list) to addToList(List<Integer> list), the compiler will prevent you from adding Strings to that list in this method.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 3
    @SaurabhJhunjhunwala You'd get a warning for using the raw List type here - `addToList(List list)` - by since you are using the raw List type, you get no compilation errors. – Eran Apr 28 '15 at 13:04
  • 1
    I believe the first line of your answer could have been the second line for a better impact? – Chetan Kinger Apr 28 '15 at 13:05
3

There are two reasons for why an exception is not thrown, depending on how we look at this particular example. Also depends on why, or where, we think an exception should be thrown. (If we think one should be thrown at all, since clearly Java thinks otherwise.)

The code in the question is tricky, even if we know why add doesn't throw an exception, because one reason has to do with the way Java chooses overloads. I'd recommend running the following program to understand better:

class Example {
    static void m(int arg) {
        System.out.println("int " + arg);
    }
    static void m(Object arg) {
        System.out.println("Object " + arg);
    }

    public static void main(String[] args) {
        m(new Integer(0));
    }
}

That prints Object 0. Why? Because Java searches for an overload in three stages:*

  1. The first stage looks for an overload without allowing boxing, unboxing and varargs.
  2. The second stage looks for an overload allowing boxing and unboxing.
  3. The third stage looks for an overload allowing boxing, unboxing and varargs.

These stages are "short-circuiting", so in the case of m(new Integer(0)), stage 1 finds the Object overload before considering the int overload which requires unboxing. Since an overload is found, stage 2 is not tried.

The same thing happens for ListIntegerDemo1, where System.out.println(Object) is chosen.

Generics are erased, which means that:

  • list.get actually returns Object.
  • To assign its result to an Integer, a cast is inserted by the compiler like (Integer) list.get(0).

But a cast does not need to be inserted to call the Object overload, so an exception doesn't throw.

Using a raw type List to add String to a List<Integer> is called heap pollution. The code in the question is an example of why heap pollution is so bad: because sometimes we don't find out about it. Sometimes we find out about it much later and do not know where the error was actually caused.

An exception would be thrown if we did this:

Integer i = list.get(0);
System.out.println(i);

Because in that case, a cast is required to do the assignment.

And also if we did this:

public class ListIntegerDemo1 {
    public static void addToList(List list) {list.add(67);list.add(0xbb);}
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        addToList(list);
        System.out.println(list.get(0));
    }     
}

Because in that case, System.out.println(String) is chosen as being more specific and a cast is inserted.

We could also prevent our List<Integer> from being polluted in unsafe code by using Collections#checkedList:

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

In that case, an exception will be thrown from the call to add inside addToList.

Of course the best solution is to not use raw types:

static void addToList(List<String> list) {
    list.add("0067");
    list.add("bb");
}

Now we can't pass a List<Integer> to the method.

addToList could also accept a List<? super String>.


* This is detailed in 15.12.2.

Community
  • 1
  • 1
Radiodef
  • 37,180
  • 14
  • 90
  • 125