15

I try to understand how is it possible to have a Double value into an ArrayList of Integer. The numList is an ArrayList of Integer, and the value from it is a Double.

This is the code:

package bounded.wildcards;

import java.util.ArrayList;
import java.util.List;

public class GenericsDemo {

    public static void main(String[] args) {
        // Invariance Workaround
        List<Integer> numList = new ArrayList<>();
        GenericsDemo.invarianceWorkaround(numList);
        System.out.println(numList);
    }

    static <T extends Number> void invarianceWorkaround(List<T> list) {

        T element = (T) new Double(23.3);  
        list.add(element);
    }

}

This will compile and run without an error.

  • 3
    Possible duplicate of [type erasure in implementation of ArrayList in Java](https://stackoverflow.com/questions/22259737/type-erasure-in-implementation-of-arraylist-in-java) –  Mar 28 '19 at 13:19
  • 1
    also, https://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens and many others... I think that someone can find an exact duplicate, but they are are extremely closely related. –  Mar 28 '19 at 13:20
  • Thank you for the links, I know about type erasure and add cast, but I cannot understand how is it possible to have an ArrayList of Integer with this Double value in it. And there is no exception when I run it. –  Mar 28 '19 at 18:58
  • 1
    @gabv you're contradicting yourself. If you knew about type erasure, you'd understand why you can add *anything* to `ArrayList` at runtime... because the answer is *because of type erasure*. As such, it follows you must either not know about type erasure mechanism in Java or not understand it, which both mean you should learn more about it. –  Mar 28 '19 at 21:20

1 Answers1

21

This is because of type erasure used with Java generics - the type checks are only performed at compile time for generic types, and the type info for generics is then erased, effectively turning List<Integer> into List<Object>.

My IDE warns you of an "Unchecked cast from Double to T". But the compiler couldn't be sure that your code is wrong, so it does not emit an error, just a warning.

Then at runtime, the type check is no longer present due to type erasure, so the code will run without error unless you perform some operation that fails due to incompatible runtime type. System.out.println() is not such operation.


If you change the print code to

Integer num = numList.get(0);
System.out.println(num);

this will now involve runtime type check and will therefore fail:

java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer

Jiri Tousek
  • 12,211
  • 5
  • 29
  • 43
  • 3
    Note that a `ClassCastException` is emitted when one tries to do this: `Integer i = numList.get(0)`. – MC Emperor Mar 28 '19 at 11:57
  • @MCEmperor Thanks, added. I couldn't find a simple example to force type incompatibility - integer seems to basically have no meaningful methods that wouldn't either be static or already in `Number`. – Jiri Tousek Mar 28 '19 at 12:06
  • 2
    @JiriTousek You may be interested in [Java is Unsound](https://hackernoon.com/java-is-unsound-28c84cb2b3f). Basically all java compilers will add cast checks in places to avoid issues because they know that the type system is broken. – Giacomo Alzetta Mar 28 '19 at 12:53
  • effectively turning into `List` not `List` because of upper bound is `Number`. if its List then you can store String but you can't – Akash Shah Mar 28 '19 at 13:00
  • @AkashShah I disagree: `List stringList = (List) numList; stringList.add("abc");`. Lots of compiler warnings, but compiles and runs nevertheless. – Jiri Tousek Mar 28 '19 at 22:44
  • @GiacomoAlzetta Thanks for sharing that, an interesting reading even though not the easiest one. – Jiri Tousek Mar 28 '19 at 23:07
  • see in [docs](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html) Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. and your example you write `(List) numList` where list class something like`List` so again type erasue to `List`. if i not wrong. – Akash Shah Mar 29 '19 at 05:32
  • i think this is `List stringList = (List) numList; stringList.add("abc");` to https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html not sure how its work – Akash Shah Mar 29 '19 at 05:57