3

Java generics are invariant so it's impossible to do such cast:

List<Object> li = (List<Object>)new ArrayList<Integer>();

But in the following code in line 4 I can cast from List<Integer> to List<T>, where T could be any type. Why is that type of cast allowed?

I know it generates warning about unchecked cast, but the point is that this cast is possible inside parametrized method, but not in normal code. Keeping in mind that generics are invariant why is it allowed? In normal code when having List<Integer> I can only cast it to List<Integer> which doesn't make sense and other casts are illegal. So what is the point of allowing such cast as in line 4?

I know that generic types are removed at compile time and it ends with List xlist = (List)list, but before removing those types it is obvious that this cast should not be allowed unless it is accepted only for the case when someone passes Integer as el which doesn't make much sense.

class Test {

    public static <T> void t(List<Integer> list, T el) {
        List<T> xlist = (List<T>)list; //OK
        xlist.add(el);
    }

    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        t(list, "a");
        t(list, "b");

        //prints [a, b] even if List type is Integer
        System.out.println(list);

    }
}
ctomek
  • 1,696
  • 16
  • 35
  • 2
    This has to do with type erasure in Java which does not preserve generic type at runtime. Java sees `List` and `List` and `List` as the same type: `List`. – callyalater May 19 '16 at 19:01
  • 2
    @callyalate Types are erased after compilation. The question is, why does compiler allow this? I wonder, too. – SqueezyMo May 19 '16 at 19:08
  • @callyalater I know, I wrote about that in last paragraph and my question is about compile time. Why is it even allowed to cast parametrized classes. – ctomek May 19 '16 at 19:09
  • Looking into this, I found that it might be due (at least in part) to pre-Java5 non-generic classes that needed to have reverse compatibility. See [this page](https://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html). – callyalater May 19 '16 at 19:16
  • @callyalater I've read that, but there is no information about any type of casting and that's the main part of my question. – ctomek May 19 '16 at 19:23
  • 1
    The compiler _in general_ allows you to turn off generic type checking. This is just one example of that. – Louis Wasserman May 19 '16 at 20:01

3 Answers3

6

In Java, it is a compile error to do an explicit cast that is known at compile time to be always incorrect or always correct. A cast from List<Integer> to List<Object> is known at compile time to be always incorrect, therefore it is not allowed. A cast from List<Integer> to List<T> is not known at compile time to be always incorrect -- it would be correct if T were Integer, and T is not known at compile time.

newacct
  • 119,665
  • 29
  • 163
  • 224
1

I guess the answer is that this cast is possible only because Integer can be passed as argument el and in that case that cast will be correct. In all other cases of types other than Integer it will be illegal cast. But as I see in generics, if there is at least one type which would give correct cast (Integer) then the compiler doesn't give an error, but warning, even if all other cases are invalid.

ctomek
  • 1,696
  • 16
  • 35
  • I've been thinking. What is the signature of `Test::t`? With type erasure (in this case), it is `public static String Test::t(List list, String el)`, right? Because the generic type for the parameter `list` is already removed by the time that a `String` is passed in for `el`. That means that `list` has probably already lost its type parameter when it is cast to a `List` and conversion from a raw type to a parameterized type is not a compiler error. When it prints the array, all it is doing is calling the `toString()` method on each object, which is why it prints `[a, b]`. – callyalater May 19 '16 at 21:06
  • 2
    @callyalater The compiler knows the type signature of the method before erasure and the question is about a compile-time, not run-time error. – Erwin Bolwidt May 20 '16 at 02:11
  • 2
    @callyalater the raw, erased type is not String, it's Object. – pvg May 20 '16 at 02:49
0

Object is a super class of the class Integer. List<Object> is not a super class of the class List<Integer> , but furthermore if you would like a List<Object> to reference to a List<Integer> you can use joker sign (?).

List<?> list1 = new List<Object>();
List<?> list2 = new List<Integer>();
list1 = list2;