Почему этот оператор не бросает StackOverflowError?
Я только что увидел этот странный фрагмент кода в другом вопросе. Я думал, что это приведет к выбросу StackOverflowError
, но это не...
public class Node {
private Object one;
private Object two;
public static Node NIL = new Node(Node.NIL, Node.NIL);
public Node(Object one, Object two) {
this.one = one;
this.two = two;
}
}
Я думал, что он взорвется, из-за Node.NIL
, ссылающегося на себя для сборки.
Я не могу понять, почему это не так.
Ответы
Ответ 1
NIL
- статическая переменная. Он инициализируется один раз, когда класс инициализируется. Когда он инициализируется, создается один экземпляр Node
. Создание этого Node
не вызывает создание каких-либо других экземпляров Node
, поэтому нет бесконечной цепочки вызовов. Передача Node.NIL
в вызов конструктора имеет тот же эффект, что и передача null
, так как Node.NIL
еще не инициализируется при вызове конструктора. Поэтому public static Node NIL = new Node(Node.NIL, Node.NIL);
совпадает с public static Node NIL = new Node(null, null);
.
Если, с другой стороны, NIL
была переменной экземпляра (и не передавалась как аргумент конструктору Node
, так как компилятор помешал бы вам передать его в конструктор в этом случае), он будет инициализирован каждый раз, когда будет создан экземпляр Node
, который создал бы новый экземпляр Node
, создание которого инициализировало бы другую переменную экземпляра NIL
, что привело бы к бесконечной цепочке вызовов конструктора, которая закончилась бы в StackOverflowError
.
Ответ 2
В переменной NIL сначала присваивается значение null
, а затем инициализируется один раз сверху вниз. Это не функция и не определяется рекурсивно. Любое статическое поле, которое вы используете до его инициализации, имеет значение по умолчанию, а ваш код такой же, как
public static Node {
public static Node NIL;
static {
NIL = new Node(null /*Node.NIL*/, null /*Node.NIL*/);
}
public Node(Object one, Object two) {
// Assign values to fields
}
}
Это ничем не отличается от написания
NIL = null; // set implicitly
NIL = new Node(NIL, NIL);
Если вы определили функцию или метод, как это, вы получите исключение StackoverflowException
Node NIL(Node a, Node b) {
return NIL(NIL(a, b), NIL(a, b));
}
Ответ 3
Ключ, чтобы понять, почему он не вызывает бесконечную инициализацию, заключается в том, что при инициализации класса Node
JVM отслеживает его и избегает повторной инициализации во время рекурсивной ссылки на класс в своей первоначальной инициализации. Это подробно описано в в этом разделе спецификации языка:
Поскольку язык программирования Java является многопоточным, инициализация класса или интерфейса требует тщательной синхронизации, поскольку некоторые другие потоки могут пытаться инициализировать один и тот же класс или интерфейс в одно и то же время. Существует также возможность того, что инициализация класса или интерфейса может быть запрошена рекурсивно как часть инициализации этого класса или интерфейса; например, переменный инициализатор в классе A может вызывать метод несвязанного класса B, который, в свою очередь, может вызвать метод класса A. Реализация виртуальной машины Java отвечает за синхронизацию и рекурсивную инициализацию с помощью следуя процедуре.
Поэтому, когда статический инициализатор создает статический экземпляр NIL
, ссылка на Node.NIL
как часть вызова конструктора не повторяет повторный запуск статического инициализатора. Вместо этого он просто ссылается на любое значение, которое имеет значение reference NIL
, которое в этом случае null
.