In the next piece of code I have a parameterized method which creates an ArrayList
, cast it to <T extends List<Integer>>
. And that list is getting assigned to a variable of type MyClass
public class MyClass {
public static void main(String ... args) {
MyClass s = createList(); // compiler should find here an error
}
private static <T extends List<Integer>> T createList()
{
return (T) new ArrayList<Integer>();
}
}
I expect that the compiler won't process that code and will show me an error. But the class gets compiled and in runtime the app predictably throws a cast exception:
Exception in thread "main" java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class java.lang.String (java.util.ArrayList and java.lang.String are in module java.base of loader 'bootstrap') at pkgname.MyClass.main(MyClass.java:9)
If I change generic's signature to T extends ArrayList
it works as I expect - shows compiling error:
Error:(9, 31) java: incompatible types: inference variable T has incompatible upper bounds pkgname.MyClass,java.util.ArrayList
So that works only with interfaces.
And if I try to assign the returning value to classes like Integer
, Number
, String
etc. or make MyClass
final, then I'm getting a warning
Though assignment is formal correct, it could lead to ClassCastException at runtime. Expected: 'String', actual: 'List<Integer> & String'
Yes, the casting shows a warning Unchecked cast: 'java.unil.ArrayList' to 'T'
but why does the compiler fails to detect wrong assinment if I use an interface as a generic's param?
I suspect that when I use an interface as a generic's bound, a compiler might assume that even though MyClass
doesn't inherit the interface yet, one of MyClass's descendants might, so that code might be correct. Buy when the class is final
- there is definitely no chance that returned value can be assigned to MyClass