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.

4
  • 3
    Possible duplicate of type erasure in implementation of ArrayList in Java Commented Mar 28, 2019 at 13:19
  • 1
    also, stackoverflow.com/questions/339699/… and many others... I think that someone can find an exact duplicate, but they are are extremely closely related. Commented Mar 28, 2019 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. Commented Mar 28, 2019 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<Double> 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. Commented Mar 28, 2019 at 21:20

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

8 Comments

Note that a ClassCastException is emitted when one tries to do this: Integer i = numList.get(0).
@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.
@JiriTousek You may be interested in Java is Unsound. Basically all java compilers will add cast checks in places to avoid issues because they know that the type system is broken.
effectively turning into List<Number> not List<Object> because of upper bound is Number. if its List<Object> then you can store String but you can't
@AkashShah I disagree: List<String> stringList = (List) numList; stringList.add("abc");. Lots of compiler warnings, but compiles and runs nevertheless.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.