2017-01-30 5 views
74

Я только что видел это weird часть кода в другом вопросе. Я думал, что это приведет к 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 ссылки себя строить.

Я не могу понять, почему это не так.

+7

Возможно, из-за 'static', но я не уверен – XtremeBaumer

+28

Что бы я ожидал, так это то, что поле' NIL' построено так, как оно было объявлено как 'new Node (null, null)', потому что когда вызывается конструктор , 'Node.NIL' еще не установлен. – khelwood

+0

@khelwood yep, основанный на ответе, я понял то же самое. –

ответ

100

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.

+0

Спасибо, вы поняли, что это работает, по-прежнему странно. Но по крайней мере я понимаю, почему он работает таким образом. –

+5

Если 'NIL' является переменной экземпляра, она не будет компилироваться с ошибкой' Не может ссылаться на поле до его определения'. –

+0

@FlorianGenser Хорошая точка. Я написал эту часть, прежде чем заметить, что 'Node.NIL' был передан конструктору. – Eran

27

Переменная 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)); 
} 
20

Ключ к Unders и почему он не вызывает бесконечную инициализацию, заключается в том, что при инициализации класса Node JVM отслеживает его, а избегает повторной инициализации во время рекурсивной ссылки на класс в рамках его первоначальной инициализации. Это подробно описано в this section of the language spec:

Поскольку язык программирования Java многопоточность, инициализация класса или интерфейса требует тщательной синхронизации, так как некоторые другие потоки могут пытаться инициализировать тот же класс или интерфейс одновременно. Существует также возможность того, что инициализация класса или интерфейса может быть запрошена рекурсивно как часть инициализации этого класса или интерфейса; например, переменный инициализатор в классе A может вызывать метод несвязанного класса B, который, в свою очередь, может вызвать метод класса A.Реализация виртуальной машины Java отвечает за синхронизацию и рекурсивную инициализацию, используя следующую процедуру.

Так в то время как статический инициализатор создает статический экземпляр NIL, ссылка на Node.NIL как часть вызова конструктора не повторно выполнить статический инициализатор снова. Вместо этого он просто ссылается на любое значение, которое имеет эталонная ссылка NIL, которая в этом случае равна null.

Смежные вопросы