Вызов 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).
С другой стороны,
Если декларатор предназначен для переменной экземпляра (то есть поля, которое не является статическим), то к его инициализатору применяются следующие правила:
- Во время выполнения инициализатор оценивается и присваивание выполняется каждый раз, когда создается экземпляр класса.
Давайте добавим 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>
методы в нем. В результате мы получаем исключение, когда размер стека достигает максимально допустимого размера.