Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting raw types w/ conflicting generic super-interfaces

I've come across an interesting bit of Java code that IntelliJ flags as an error, but which javac accepts as legal. Either IntelliJ is wrong, and the code is legal, or the compiler is "wrong", whether due to a bug or an intentional relaxation of rules.

I like to think I understand the Java type system pretty well, and my own reasoning leads me to suspect that IntelliJ is wrong and javac is right. However, I have a heck of a time grokking the JLS, and I'd like to know for sure.

Before we get into the problematic code, let's look at some similar code that is definitely illegal:

interface A<T> {}
interface X extends A<String> {}
interface Y extends A<Object> {}

interface Z extends X, Y {} // COMPILE ERROR

As I would expect, both IntelliJ and javac correctly flag this as an error: 'A' cannot be inherited with different type arguments: 'java.lang.String' and 'java.lang.Object'.

No problem there. But what if we make X and Y generic, with Z extending their raw form?

interface X<T> extends A<String> {}
interface Y<T> extends A<Object> {}

interface Z extends X, Y {}  // OK according to javac, ERROR according to IntelliJ

Here, IntelliJ eagerly reports the same error it did for the first snippet, but javac happily accepts the code as written.

My understanding is that raw types are recursively erased, meaning all superclasses and superinterfaces of the original type are replaced with their recursively erased forms, and so on. Thus, in the problematic code, Z ends up extending (raw) A via both X and Y, as opposed to first example, in which Z extends A<String> via X and A<Object> via Y.

If that is indeed the case, then I would conclude that IntelliJ is wrong, and javac is correct: the second code snippet is legal.

What say you, experts of Stack Overflow?

like image 280
Mike Strobel Avatar asked Feb 06 '18 18:02

Mike Strobel


1 Answers

The spec says in JLS-8.1.5:

A class may not at the same time be a subtype of two interface types which are different parameterizations of the same generic interface (§9.1.2), or a subtype of a parameterization of a generic interface and a raw type naming that same generic interface, or a compile-time error occurs.

Note that it makes a special note of the case of "a subtype of a parameterization of a generic interface and a raw type naming that same generic interface", which would translate to something like interface Z extends A<String>, A {}. The case of 2 raw superinterfaces is not mentioned.

Furthermore, the spec gives this example:

interface I<T> {}
class B implements I<Integer> {}
class C extends B implements I<String> {}

Class C causes a compile-time error because it attempts to be a subtype of both I<Integer> and I<String>.

The problem is that A is extended with different type arguments, if X and Y both were to extend A<Object> in your first snippet that also compiles.

JLS-4.8 says (as you already mentioned):

The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.

That means that Z extends the raw type A twice, not A with different parametrizations. Moreover, it is fine for a class to have the same (indirect) superinterface twice (see the second example in JLS-8.1.5-2).

So I conclude that Intellij is wrong here.

like image 143
Jorn Vernee Avatar answered Sep 19 '22 06:09

Jorn Vernee