这堂课:
public class OuterChild extends OuterChild.InnerParent {
public static class InnerParent {
}
}
无法编译:
$ javac OuterChild.java
OuterChild.java:1: error: cyclic inheritance involving OuterChild
public class OuterChild extends OuterChild.InnerParent {
^
1 error
因为OuterChild
会依赖"本身,因为(每§8.1.4 "Superclasses and Subclasses" of The Java Language Specification, Java SE 8 Edition)一个类直接依赖于[其] extends
或implements
子句[...]中提到的任何类型作为完全限定词超类或超界面名称的限定形式。"
但我真的不明白这里的动机。什么是有问题的依赖?它只是为了与InnerParent
是非static
的情况保持一致(并因此最终得到一个词汇封闭的实例)?
答案 0 :(得分:5)
这似乎是一个相当邪恶的角落,因为有一个number of bugs与循环继承有关,经常导致无限循环,堆栈溢出和编译器中的OOM。以下是一些可能提供一些见解的相关引用:
此示例不合法,这在即将发布的内容中已明确说明 Java语言规范的第2版。同时上课 但是,继承和封闭相关的问题是有问题的 最初的内部白皮书没有充分解决这个问题, 1.3之前的编译器也没有实现一致的策略。在JLS第二期 版本,反循环继承的规则已被扩展到禁止 来自"取决于"的类或接口本身,直接或间接地。 类型不仅取决于它扩展或实现的类型,还取决于它 在这些类型的名称中充当限定符的类型。
两个类声明确实是循环的;相应于JLS 8.1.4,我们有:
Foo依赖于Foo $ Intf(Foo $ Intf出现在Foo的implements子句中)
Foo $ Intf取决于Moo $ Intf(Moo $ Intf出现在Foo $ Intf的extends子句中)
Foo $ Intf依赖于Foo(Foo在Foo $ Intf的extends子句中显示为限定符)对于传递性,我们认为Foo依赖于自身;因此,代码应该被编译时错误拒绝。
退一步,在JLS2中引入了类和接口的直接依赖关系,以阐明JLS1并涵盖嵌套类的超类/超接口(例如,描述中的A.B)。
这个问题是由于javac执行类型变量边界属性wrt类属性的顺序。
1)类Outer< T的归属延伸了Outer.Inner>
1a)外部的归因触发了外部类型变量的归属 2)外部归因.T
2a)Outer.T的归因触发其声明的界限的归属 3)类Outer.Inner的归属< S延伸T>
3a)Outer.Inner的归因触发了Outer.Inner类型变量的归属 4)Outer.Inner< S>的归属 4a)Outer.Inner.S的归因触发了其声明的边界的归属 5)Outer.T的归因 - 这只会返回T的类型;正如您所看到的,在此阶段尚未在表示T类型的对象上设置T绑定。稍后,对于每个属性类型变量,javac执行检查以确保给定类型变量的边界不会引入循环继承。但我们已经看到Outer.T没有设定界限;因为这是javac在尝试检测由Outer.Inner.S声明的边界引起的继承树中的循环时与NPE崩溃的原因。
类型变量边界可能引用属于循环继承树的类,这会导致解析过程在查找符号时进入循环。
针对您的具体问题" 什么是有问题的依赖?"它似乎是一个复杂的编译时符号解析边缘情况,JLS2中引入的解决方案是简单地禁止由限定符类型引入的循环以及实际的超类型。
换句话说,理论上可以对编译器进行适当的改进,但是直到有人出现并使这种情况发生,在语言规范中禁止这种不寻常的关系更为实际。