3

There is a question How to Convert List<String> to List<Object> and many similar questions about List<P> and List<C> with C extends P.

Obviously, we cannot using cast like this:

List<String> list0 = Arrays.asList("abc", "xyz");
List<Object> list1 = (List<Object>) list0; //ERROR here

the accepted answer in above question is:

List<Object> objectList = new ArrayList<Object>(stringList);

but when I try this code:

List<String> list0 = Arrays.asList("abc", "xyz");
List<Object> list1 = (List<Object>) (Object) list0;
System.out.println(list1);

Even like this:

List<String> list0 = Arrays.asList("abc", "xyz");
List<Integer> list1 = (List<Integer>) (Object) list0;
System.out.println(list1);

It runs successfully, so we can cast indirectly! I really doubt this! any acceptable reason for this?

Jason Law
  • 965
  • 1
  • 9
  • 21
yelliver
  • 5,648
  • 5
  • 34
  • 65

4 Answers4

2

This is (more or less) to be expected. Java generics only exist in the compiler, not at runtime. The compiler checks whether everything works out, and then erases the types so only Object (or whatever lower bound) remains.

If you cast to Object along the way, you're no longer casting one generic type to another directly, and the compiler can't verify anything, so it's permitted. And since the generic types no longer exist at runtime it also works. But you may upset code using that list because you can now add Objects instead of just Strings. That double-cast in the way you're doing it is likely fine when you're only enumerating the list, but if possible I'd avoid doing that in either case. Type safety is a nice thing.

With your cast to List<Integer> exactly the same thing is happening. The reason it still works is because you're just printing the list (not sure whether that enumerates and writes the list's contents in Java, but if it does, then it will likely call toString() on the elements anyway. Since toString is a method on Object there is no difference whether you call it on an Integer or a String. The resulting bytecode is the same. However if you called methods of Integer instead, then you'll definitely get a crash at runtime, even though the compiler, as noted before, can't save you from doing stupid things.

Joey
  • 344,408
  • 85
  • 689
  • 683
2

Writing:

List<Integer> list1 = (List<Integer>) (Object) list0;

is equivalent to:

Object obj = (Object) list0;
List<Integer> list1 = (List<Integer>) obj;

As far as the compiler is concerned, casting any reference type to Object is always valid (since all reference types are Objects). The second cast is also allowed, since as far as the compiler is concerned, the Object reference may hold (in run-time) a reference to any reference type, so it will allow you can cast to any type (even to Set<Integer>, which will pass compilation and only fail in run-time).

Now, at run-time, after generic type parameters are erased, your code becomes:

List list0 = Arrays.asList("abc", "xyz");
List list1 = (List) (Object) list0;
System.out.println(list1);

Both casts are valid in run-time, so your code doesn't cause a ClassCastException.

Eran
  • 387,369
  • 54
  • 702
  • 768
1

Casts from Generic<Subclass> to Generic<SuperClass> are unsafe, but most developers do not care about this. Look at following example classes

class Fruit {
}
class Apple extends Fruit {
}

Where an Apple always can be used as Fruit a Bowl<Apple> cannot be used as a Bowl<Fruit>. If I cast Bowl<Apple> to Bowl<Fruit>, I may later on add an Orange to the fruit bowl and the original apple bowl will contain an orange. Example:

Bowl<Apple> apples = new Bowl<Apple>();
...
Bowl<Fruit> fruit = (Bowl<Apple>) apples; // no exception
fruit.add(new Orange()); // no exception
...
Apple apple = apples.get(0); // no compiler warning, but exception

So the purpose of not allowing a cast is probably to remind the user that he is probably doing something that he does not understand.

Yet the compiler allows us to cast Fruit to Apple. This is a limitation of Java (other languages allow only safe casts (with type checks ensuring that the cast is possible)), but it is not that bad as generic casts, because the exception is thrown at the location where the illegal cast is done - while with generic casts the exception may occur even in another classes method.

Yet there are scenarios where we know that a fruit bowl will only contain apples (also in future) or a list will always contain Strings (e.g. if the data is not modifiable). In such scenarios, I would prefer the idiom:

List<Object> list1 = (List<Object>) (List) list0;
CoronA
  • 7,717
  • 2
  • 26
  • 53
0

List and List are incompatible. But you can write:

List<Object> list1 = (List<Object>)(List<?>)  list0;
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24