我遇到了一些有趣的Java代码,IntelliJ标记为错误,但javac
认为是合法的。 IntelliJ是错误的,代码是合法的,或者编译器是“错误的”,无论是由于错误还是故意放松规则。
我觉得我很了解Java类型系统漂亮,而我自己的推理让我怀疑IntelliJ是错误的,javac
是对的。但是,我有一段时间来讨论JLS,我想肯定地知道。
在我们讨论有问题的代码之前,让我们看一下类似的代码肯定是非法的:
interface A<T> {}
interface X extends A<String> {}
interface Y extends A<Object> {}
interface Z extends X, Y {} // COMPILE ERROR
正如我所料,IntelliJ和javac
正确将此标记为错误:'A'不能使用不同的类型参数继承:'java.lang.String '和'java.lang.Object'。
没问题。但是,如果我们X
和Y
通用,Z
扩展原始格式会怎样?
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
在这里,IntelliJ急切地报告它对第一个片段所犯的错误,但是javac
乐意接受所写的代码。
我的理解是原始类型被递归擦除,这意味着原始类型的所有超类和超接口都被它们的递归擦除形式替换,依此类推。因此,在有问题的代码中,Z
最终通过A
和X
扩展(原始)Y
,而不是第一个例子,其中Z
通过A<String>
和X
通过A<Object>
扩展Y
。
如果情况确实如此,那么我会断定IntelliJ是错误的,并且javac
是正确的:第二个代码段是合法的。
什么说你,Stack Overflow的专家?
答案 0 :(得分:6)
规范在JLS-8.1.5中说明:
一个类可能不会同时是两个接口类型的子类型,这两个接口类型是同一通用接口(第9.1.2节)的不同参数化,或者是通用接口的参数化的子类型和原始类型命名相同的通用接口,或发生编译时错误。
请注意,它特别注意“通用接口参数化的子类型和命名相同通用接口的原始类型”的情况,它将转换为类似{{ 1}}。没有提到2个原始超接口的情况。
此外,规范给出了这个例子:
interface Z extends A<String>, A {}
C类会导致编译时错误,因为它会尝试成为
interface I<T> {} class B implements I<Integer> {} class C extends B implements I<String> {}
和I<Integer>
的子类型。
问题是如果I<String>
和A
两者都在X
扩展Y
,A<Object>
会使用不同的类型参数进行扩展也编译的片段。
JLS-4.8说(如你所说):
原始类型的超类(分别是超级接口)是泛型类型的任何参数化的超类(超接口)的擦除。
这意味着Z
将原始类型A
扩展两次,不 A
使用不同的参数化。此外,一个类可以两次使用相同的(间接)超级接口(参见JLS-8.1.5-2中的第二个示例)。
所以我得出结论,Intellij在这里错了。