Почему класс не может вставлять в него статический вложенный класс?

Этот класс:

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 будет "зависеть" от себя, потому что (за & sect; 8.1.4 "Суперклассы и подклассы" Спецификации языка Java, Java SE 8 Edition), класс напрямую зависит от любого типа, который "упоминается в [его] extends или implements clause [& hellip;] как квалификатор в полностью квалифицированной форме суперкласса или имени суперинтерфейса."

Но я действительно не понимаю мотивацию здесь. Какая проблемная зависимость? Это просто для согласованности с случаем, когда InnerParent были не static (и поэтому заканчивались бы лексически охватывающим экземпляром самого себя)?

Ответ 1

Это кажется довольно гнусным угловым случаем, так как существует количество ошибок, связанных с циклическим наследованием, часто приводящее к бесконечным циклам, переполнение стека и OOM в компиляторе. Вот некоторые релевантные цитаты, которые могут дать некоторое представление:

Ошибка 4326631:

Этот пример не является законным, и это ясно изложено в предстоящем 2-е издание Спецификации языка Java. Классы одновременно связанные как наследованием, так и вложением, являются проблематичными, однако в первоначальном документе, посвященном домашнему классу, не было должным образом рассмотрено вопрос, и компиляторы до 1.3 не применяли согласованную политику. В JLS 2nd издание, правило против циклического наследования было расширено, чтобы запретить класс или интерфейс от "зависящего" от себя, прямо или косвенно. Тип зависит не только от типов, которые он распространяет или реализует, но также и от типы, которые служат квалификаторами в именах этих типов.

Ошибка 6695838:

Два объявления класса действительно цикличны; в соответствии с JLS 8.1.4 мы имеем, что:

Foo зависит от Foo $Intf (Foo $Intf появляется в предложении инвентаря Foo)
Foo $Intf зависит от Moo $Intf (Moo $Intf появляется в предложении extends Foo $Intf)
Foo $Intf зависит от Foo (Foo появляется как определитель в предложении extends Foo $Intf)

Для транзитивности мы имеем, что Foo зависит от самого себя; поэтому такой код должен быть отклонен с ошибкой времени компиляции.

Ошибка 8041994:

Отступая назад, в JLS2 была введена зависимость прямого отношения для классов и интерфейсов, чтобы уточнить JLS1 и охватить суперклассы/суперинтерфейсы, которые являются вложенными классами (например, A.B в описании).

Ошибка 6660289:

Эта проблема возникает из-за порядка, в котором javac выполняет атрибуцию границ переменной типа по атрибуту класса.

1) Атрибуция класса Outer < T extends Outer.Inner >
1a) Атрибуция атрибутов внешних триггеров переменной Outer type
2) Атрибуция Outer.T
2а) Приписывание триггеров Outer.T атрибуции объявленной связанной
3) Атрибуция класса Outer.Inner < S extends T >
3a) Атрибуция Outer.Inner вызывает атрибуцию переменной Outer.Inner type
4) Атрибуция Outer.Inner <S>
4a) Атрибуция Outer.Inner.S запускает атрибуцию объявленной связанной
5) Атрибуция Outer.T - это ничего, кроме возвращения типа T; как вы можете видеть, на этом этапе T bound еще не установлен на объекте, представляющем тип T.

В более поздней точке для каждой переменной присваиваемого типа javac выполняет проверку, чтобы гарантировать, что граница данной переменной типа не вводит циклическое наследование. Но мы видели, что для Outer.T не установлена ​​никакая граница; потому что это вызвано тем, что javac падает с NPE при попытке обнаружить цикл в дереве наследования, вызванный объявленной границей Outer.Inner.S.

Ошибка 6663588:

Границы переменной типа могут относиться к классам, принадлежащим дереву циклического наследования, что заставляет процесс разрешения вводить цикл при поиске символов.

К вашему конкретному вопросу о том, "что такое проблемная зависимость?" он представляется сложным краем случая разрешения символа компиляции, а решение, введенное в JLS2, состояло в том, чтобы просто запретить циклы, введенные типами классификаторов, а также фактическими супертипами.

Другими словами, теоретически это можно было бы сделать с соответствующими улучшениями в компиляторе, но пока кто-то не придет и сделает это более практичным, просто запретите это необычное отношение в спецификации языка.

Ответ 2

Образованный SWAG: поскольку JVM должен сначала загрузить родительский класс, который включает команду для загрузки внутреннего класса. Внутренний класс определяется CL после определения внешнего класса, поэтому любые ссылки на поля или методы внешнего класса разрешимы. Пытаясь распространить внешнюю на внутреннюю, он просит JVM скомпилировать внутреннее перед внешним, создав тем самым проблему с курицей и яйцом. Проблема уходит корнями в то, что внутренний класс может ссылаться на свои значения полей внешнего класса, при условии соблюдения правил вокруг области и экземпляра (статический v. Нестатический). Из-за этой возможности JVM необходимо гарантировать, что ни разу во внутреннем классе не будет пытаться получить доступ или изменить какие-либо ссылки на поле или объект во внешнем классе. Он может найти это только путем компиляции обоих классов, во-первых, сначала, но перед этой компиляцией эта информация должна быть уверенной, что не будет какой-либо проблемы с объемом или экземпляром. Так что это уловка-22.