为什么类不能扩展其中发生的静态嵌套类?



此类:

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将"依赖"它自己,因为(根据Java语言规范,Java SE 8版的§8.1.4"超类和子类")一个类直接依赖于"在[its]extendsimplements子句[…]中以超类或超接口名称的完全限定形式提到的限定符"的任何类型

但我并不真正理解这里的动机。有问题的依赖关系是什么?这只是为了与InnerParent不是static的情况保持一致吗(因此最终会有一个词汇封闭的自身实例)?

这似乎是一个相当邪恶的角落案例,因为存在许多与循环继承有关的错误,通常会导致编译器中的无限循环、堆栈溢出和OOM。以下是一些相关的报价,可以提供一些见解:

错误4326631:

这个例子不合法,这一点在即将发布的Java语言规范第2版。同时上课然而,通过继承和封闭来关联是有问题的最初的innerclasses白皮书没有充分解决这个问题,1.3之前的编译器也没有实施一致的政策。在JLS第二版本中,禁止循环继承的规则已扩展为禁止直接或间接地依赖于自身的类或接口。类型不仅依赖于它扩展或实现的类型,而且还依赖于在这些类型的名称中充当限定符的类型。

错误6695838:

这两个类声明确实是循环的;根据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依赖于它自己;因此,应该以编译时错误拒绝代码。

Bug 8041994:

退一步讲,类和接口的直接依赖关系在JLS2中被引入,以澄清JLS1并涵盖作为嵌套类的超类/超接口(例如说明书中的A.B)。

错误6660289:

这个问题是由于javac执行类型变量边界的归因wrt类归因的顺序造成的。

1) 类归属Outer<T向外延伸。内>
1a)Outer的归因触发Outer类型变量的归因
2) 外部.T的归因
2a)Outer.T的归因触发其声明边界的归因
3) 类的归属Outer.Inner<S扩展T>
3a)外部归因。内部触发外部归因。内的类型变量
4) Outer.Inner<S>
4a)Outer.Inner.S的归因触发其声明绑定的归因
5) 外T的归属——这只会返回T的类型;正如你所看到的,在这个阶段,T的界限还没有在代表T类型的对象上设置。

稍后,对于每个属性类型变量,javac都会执行一次检查,以确保给定类型变量的绑定不会引入循环继承。但我们已经看到,外层T没有界限;这就是javac在试图检测由Outer.Inner.S.的声明绑定引起的继承树中的循环时与NPE崩溃的原因

错误6663588:

类型变量边界可能指属于循环继承树的类,循环继承树会导致解析过程在查找符号时进入循环。

对于您的特定问题"什么是有问题的依赖项?",这似乎是一个复杂的编译时符号解析边缘情况,JLS2中引入的解决方案是简单地禁止限定符类型和实际超类型引入的循环。

换言之,理论上可以对编译器进行适当的改进,但在有人出现并实现这一点之前,在语言规范中禁止这种不寻常的关系更为实际。

一个受过教育的SWAG:因为JVM必须首先加载父类,其中包括一个加载内部类的命令。内部类由CL定义,在之后定义外部类,因此对外部类的字段或方法的任何引用都是可解析的。通过尝试按内部扩展外部,它要求JVM先编译内部,然后再编译外部,从而产生了一个鸡和蛋的问题。这个问题的根源在于,如果遵循范围和实例化(静态与非静态)的规则,内部类可能会引用其外部类的字段值。由于这种可能性,JVM需要保证内部类中的任何东西在任何时候都不会试图访问或更改外部类中的字段或对象引用。它只能通过编译两个类来找到这一点,首先是外部类,但在编译之前需要这些信息,以确保不会出现某种范围或实例问题。所以这是第二十二条军规。

最新更新