Java: класс, наследующий себя - PullRequest
8 голосов
/ 24 марта 2011

Я знаю, что это бессмысленно: я просто нахожу это забавным и хочу больше узнать о механике того, что происходит, когда вы создаете класс, который наследует себя, что приводит к краху переполнения стека. Удивительно, что Java позволяет вам создать такую ​​конструкцию для начала.

Я просто догадываюсь, но JVM помещает себя в бесконечный цикл, пытаясь разрешить класс перед его созданием, или это на самом деле бесконечные экземпляры класса?

Я должен был быть более конкретным; Я использую внутренний класс для наследования от включающего класса.

 public class Outside {
    private int outsideValue;

    public class Inside extends Outside {
        private int insideValue;
        public Inside(int val) {
            insideValue = val;
        }
    }

    public Outside() {
        Inside o = new Inside(0);
    }
}

public class Main {
    public static void main(String args[]) {
        Outside o = new Outside();
    }
}

Ответы [ 8 ]

8 голосов
/ 24 марта 2011

Помните, что, поскольку Inside расширяет Outside, он имеет неявный вызов super(), который является конструктором Outside (который в свою очередь вызывает конструктор Inside)и так и происходит.

Код, который вы разместили , концептуально не отличается от следующей программы :

class A {
    B b = new B();
}

class B extends A {
}

public class Test {
    public static void main(String[] args) {
        new A(); // Create an A...
                 //   ... which creates a B
                 //   ... which extends A thus implicitly creates an A
                 //   ... which creates a B
                 //   ...
    }
}
2 голосов
/ 24 марта 2011

В своей окончательной форме эта проблема не имеет ничего общего с циклическим наследованием и внутренними классами. Это просто бесконечная рекурсия, вызванная несвязанным рекурсивным вызовом конструктора. Тот же эффект может быть продемонстрирован на следующем простом примере:

public class A {
    public A() {
        new A();
    }
}

Обратите внимание, что этот код является абсолютно допустимым, поскольку Java не накладывает никаких ограничений на рекурсивные вызовы.

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

1 голос
/ 24 марта 2011

Пример, который вы опубликовали, может стать проблематичным, если мы изменим его немного больше:

public class Outside {

    public class Inside extends Outside {

            public Inside(int val) {
        }

    }

    private Inside i;

    public Outside() {
        i = new Inside();
    }
}

Но это на самом деле не связано с тем, что Inside является внутренним классом Outside, этомогло произойти с отдельными классами верхнего уровня одинаково.

1 голос
/ 24 марта 2011

Java-компилятор не собирается входить в бесконечный цикл при попытке войти в циклическую цепочку наследования.В конце концов, каждая цепочка наследования является в конечном итоге конечным графом (и, с вычислительной точки зрения, с очень небольшим числом узлов и ребер.) Точнее, граф наследования от подкласса A к (возможному) суперклассу Z должен быть строкой (а ненаоборот), и компилятор может легко определить, является ли это строкой или нет.

Программе не требуется много времени, чтобы определить, является ли такой маленький граф циклическим или нет, или если онявляется ли строка или нет, что делает компилятор.Таким образом, компилятор не входит в бесконечный цикл, и JVM никогда не исчерпывает пространство стека, так как 1) ни компилятор не работает на JVM, ни 2) JVM не запускается (так как ничего не компилируется, и компилятор никогда не вызываетв таких условиях JVM в любом случае.)

Я не знаю ни одного языка, который бы разрешал такие циклические графы наследования (но я ничего не делал, кроме Java в течение 11 лет, так что моя память о чем-то кроме Javaявляется мягким.) Кроме того, я не вижу использования такой конструкции (в моделировании или в реальной жизни).Хотя теоретически это может быть интересно.

edit

Хорошо, я запустил ваш код, и это действительно вызывает переполнение стека.Ты был прав.Мне придется сесть и по-настоящему изучить это, чтобы понять, почему компилятор допускает такую ​​конструкцию.

Отличная находка !!!!

1 голос
/ 24 марта 2011

Расширение себя порождает ошибку циклического наследования (чего не допускает Java).Ваш пример кода компилируется и действителен.


Из-за настойчивости Владимира Иванова я исправлю свою правку.

Ваш код выдает StackOverflowError, потому чтоиз следующего:

Inside o = new Inside(0);

Поскольку Inside расширяет Outside, Inside сначала вызывает метод super() неявно (так как вы не вызывали его самостоятельно).Outside() конструктор инициализирует Inside o и цикл запускается снова, пока стек не заполнится и не переполнится (слишком много Inside и Outside внутри стека кучи).

Надеюсь, это особенно поможет ВладимируИванов.

1 голос
/ 24 марта 2011

Попробуйте в IDE, например, eclipse, это не позволит вам сделать это. т.е. выдает ошибку, подобную этой.

Обнаружен цикл: тип Test не может расширять / реализовывать сам или один из его собственных типов элементов

0 голосов
/ 24 марта 2011

Когда я пытаюсь скомпилировать:

class A extends A {
}

Я получаю:

$ javac A.java
A.java:1: cyclic inheritance involving A
class A extends A {
^
1 error

Так что Java не позволяет вам делать подобные вещи. Для информации java version "1.6.0_24"

0 голосов
/ 24 марта 2011

Вы можете получить ответ по:

Class.forName("MyClass");

Таким образом, он решается, но не создается.Таким образом, вы можете проверить, если само разрешение вызывает сбой.

Полагаю, это зависит от используемой вами JVM.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...