Чому це твердження не викидає StackOverflowError?


74

Я щойно побачив цей дивний шматок коду в іншому питанні. Я думав, що це призведе до 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

@khelwood так, на основі відповіді я зрозумів те саме.
Ентоні Реймонд,

4
Будь ласка, ніколи не використовуйте це у виробничому коді. Це може дати приємні дрібниці, але я вважав би це навмисною затуманою.
чи

@chi я точно не буду, але я бачив цей фрагмент коду з іншого питання, і мене бентежило, як це може працювати.
Ентоні Реймонд,

Відповіді:


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конструктору, оскільки компілятор заважав би вам передати її конструктору в такому випадку), вона буде ініціалізована кожного разу, коли екземпляр of Node, який створив би новий Nodeекземпляр, створення якого ініціювало б іншу NILзмінну екземпляра, що призвело б до нескінченного ланцюжка викликів конструктора, який закінчувався б StackOverflowError.


Дякую, ви зробили це ясніше, мені все одно дивно, як це працює. Але принаймні я розумію, чому це працює саме так.
Ентоні Реймонд,

5
Якби NILбула змінною екземпляра, вона не компілювалася б з помилкою Cannot reference a field before it is defined.
Флоріан Генсер

@FlorianGenser Хороший момент. Я написав цю частину, перш ніж помітити, що Node.NILїї передали конструктору.
Еран

3
java.awt.Color насправді є гарною демонстрацією статичних змінних у дії. Він має купу різних кольорів, таких як Color.BLUE, який також містить посилання на всі інші кольори ... Це мене вразило, коли я вперше запустив Java.
sfdcfox

Дякую за це @sfdcfox, я подивлюсь на це.
Ентоні Реймонд,

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

Ключ до розуміння того, чому це не викликає нескінченної Nodeініціалізації, полягає в тому, що коли клас ініціалізується, JVM відстежує це і уникає повторної ініціалізації під час рекурсивного посилання на клас в межах його початкової ініціалізації. Це докладно описано в цьому розділі специфікації мови :

Оскільки мова програмування Java багатопотокова, ініціалізація класу або інтерфейсу вимагає ретельної синхронізації, оскільки інший потік може намагатися ініціалізувати той самий клас або інтерфейс одночасно. Існує також можливість того, що ініціалізація класу або інтерфейсу може бути запитана рекурсивно як частина ініціалізації цього класу або інтерфейсу ; наприклад, ініціалізатор змінних у класі A може викликати метод не пов'язаного класу B, який, у свою чергу, може викликати метод класу A. Реалізація віртуальної машини Java відповідає за турботу про синхронізацію та рекурсивну ініціалізацію за допомогою наступна процедура.

Отже, поки статичний ініціалізатор створює статичний екземпляр NIL, посилання на Node.NILяк частину виклику конструктора не повторно виконує статичний ініціалізатор знову. Натомість він просто посилається на будь-яке значення, яке посилання NILмає на той момент, яке є nullв даному випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.