Вызов Java-конструктора того же класса из нестатического контекста приводит к рекурсии, но со статическим он работает нормально?

Я пытаюсь понять порядок инициализации класса Java. В частности, когда и в каком порядке выполняются статические и инициализатор / поля экземпляра. Я придумал пример, как в этом вопросе stackru. Почему добавление static к вызову self constructor не дает коду перейти в рекурсию.

public class Test {
    public static void main(String a[]) {
        Cons1 c1 = new Cons1();
    }
}

class Cons1 {
    static Cons1 c = new Cons1(); /* if static is removed then recursion 
                                    occurs */
    Cons1() {
         //does something
    }
}

Есть ли какая-то конкретная причина для такой разницы в поведении между статическим и экземпляром контекста. Я прошел детальную процедуру инициализации Java doc, но не смог понять, в чем логика такого поведения. Любое объяснение или ссылка на спецификации JLS будут полезны.

PS: я прошел этот пост аналогичного стека overoverflow, однако я не могу получить свой ответ оттуда.

1 ответ

Решение

Если поле объявлено как статическое, существует ровно одно воплощение поля, независимо от того, сколько экземпляров (возможно, ноль) класса может быть в конечном итоге создано. Статическое поле, иногда называемое переменной класса, воплощается, когда класс инициализируется (§12.4).

JLS 10 - 8.3.1.1. статические поля

С другой стороны,

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

  • Во время выполнения инициализатор оценивается и присваивание выполняется каждый раз, когда создается экземпляр класса.

JLS 10 - 8.3.2. Инициализация поля


Давайте добавим println Заявление, чтобы увидеть всю картину:

class Cons1 {
    static Cons1 c = new Cons1();

    Cons1() {
        System.out.println("the constructor was called");
    }

    public static void main(String[] args) {
        Cons1 c1 = new Cons1();
        Cons1 c2 = new Cons1();
    }
}

Он выдает "конструктор был вызван" три раза:

1 - когда класс был загружен и статическое поле c получил инициализацию;
2 - когда c1 был создан;
3 - когда c2 был создан.

Теперь мы сравним это с примером с полем экземпляра:

class Cons1 {
    Cons1 c = new Cons1();

    Cons1() {
        System.out.println("the constructor was called");
    }

    public static void main(String[] args) {
        Cons1 c1 = new Cons1();
    }
}

Очевидно, что это не сработает со следующей печатью трассировки стека:

Exception in thread "main" java.lang.StackruError
    at Cons1.<init>(Cons1.java:33)
    ...
    at Cons1.<init>(Cons1.java:33)

Причина в том, что каждый экземпляр Cons1 требует другого Cons1 объект. Таким образом, мы переполняем стек вызовов толкая Cons1.<init> методы в нем. В результате мы получаем исключение, когда размер стека достигает максимально допустимого размера.

Другие вопросы по тегам