1

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

Michel_T.
  • 2,741
  • 5
  • 21
  • 31
  • Good explanation why `javac` allows this for methods and not for types is in [this answer](https://stackoverflow.com/a/38095037/1602555) – Karol Dowbecki Nov 25 '19 at 10:29

1 Answers1

2

There could be a T that both extends List<Integer> and extends MyClass (for example a class MyListClass extends MyClass implements List<Integer>.

The compiler doesn't look into the createList method to figure out what it returns, it just trusts that it does what the prototype describes.

Since you use an unchecked cast inside the method body, you are allowed to break the type system this way. And that's why you get a warning about that unchecked cast.

Effectively the unchecked cast means you disable the type check at that line and therefore can implement a return type that you don't actually fulfill (you don't actually return a MyListClass as described above, you just claim that you do).

The reason this doesn't work for non-interface types is that the type system doesn't allow a type that is two unrelated non-interface types at the same time (a thing can't be a String and a MyClass at the same type).

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • Yes, the last paragraph in my question - exactly such assuming, but why can't `javac` detect a case when a class is `final`? In that case the class definitely does not `implements List` and it can't have any descendants which would implement the interface – Michel_T. Nov 25 '19 at 11:34
  • @Michel_T: it probably *could*, but the specs don't require it to. There's multiple reasons not to enforce that: 1.) it might be expensive to compute that information and requiring that from all compilers for little benefit is not worth it. 2.) there *might* be a situation where the `final` state of a class might change in other revisions (a `final` class turning non-`final` is [not considered to break binary compatibility](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.2)!). – Joachim Sauer Nov 25 '19 at 11:45
  • That's an intersting point about `final` compatibility. I've never met it before. Thanks for the opinion. – Michel_T. Nov 26 '19 at 19:33