2017-01-30 3 views
74

Ich sah gerade seltsam Stück Code in einer anderen Frage. Ich dachte, es in einem StackOverflowError geworfen würde, aber es funktioniert nicht ...Warum wirft diese Anweisung keinen 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; 
    } 
} 

Ich dachte, es explodieren würde, wegen der Node.NIL Referenzierung selbst zu bauen.

Ich kann nicht herausfinden, warum es nicht geht.

+7

wahrscheinlich wegen 'static', aber ich bin nicht sicher – XtremeBaumer

+28

Was ich erwarten würde ist, dass das 'NIL'-Feld so konstruiert ist, wie es als' neuer Knoten (null, null)' deklariert wurde, weil der Konstruktor aufgerufen wird , 'Node.NIL' wurde noch nicht auf etwas gesetzt. – khelwood

+0

@Khelwood Yep, auf der Grundlage der Antwort habe ich das gleiche Denken verstanden. –

Antwort

100

NIL ist eine statische Variable. Es wird einmal initialisiert, wenn die Klasse initialisiert wird. Wenn es initialisiert wird, wird eine einzelne Node Instanz erstellt. Die Erstellung dieser Node löst keine anderen Node Instanzen aus, so dass es keine unendliche Kette von Aufrufen gibt. Die Übergabe von Node.NIL an den Konstruktoraufruf hat dieselbe Wirkung wie die Übergabe von null, da Node.NIL beim Aufruf des Konstruktors noch nicht initialisiert ist. Daher ist public static Node NIL = new Node(Node.NIL, Node.NIL); das gleiche wie public static Node NIL = new Node(null, null);.

Wenn NIL dagegen eine Instanzvariable war (und nicht als Argument an den Node-Konstruktor übergeben wurde, da der Compiler in diesem Fall die Übergabe an den Konstruktor verhindert hätte), wäre dies der Fall jedes Mal initialisiert werden, wenn eine Instanz von Node erstellt wurde, wodurch eine neue Node Instanz erstellt würde, deren Erstellung eine weitere NIL Instanzvariable initialisiert, die zu einer unendlichen Kette von Konstruktoraufrufen führt, die in StackOverflowError enden würden.

+0

Danke, du hast es klarer gemacht, die Art und Weise wie es funktioniert ist immer noch seltsam für mich. Aber zumindest verstehe ich, warum es so funktioniert. –

+5

Wenn 'NIL' eine Instanzvariable ist, wird sie nicht mit dem Fehler' Kann nicht auf ein Feld verweisen, bevor sie definiert wird 'kompiliert. –

+0

@FlorianGenser Guter Punkt. Ich habe diesen Teil geschrieben, bevor ich bemerkte, dass 'Node.NIL' an den Konstruktor übergeben wurde. – Eran

27

Die Variable NIL erhält zuerst den Wert null und wird dann von oben nach unten initialisiert. Es ist keine Funktion und ist nicht rekursiv definiert. Jede statische Feld verwenden Sie, bevor es initialisiert wird, hat den Standardwert und Ihr Code ist der gleiche wie

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 
    } 
} 

Das ist nicht anders

NIL = null; // set implicitly 
NIL = new Node(NIL, NIL); 

zu schreiben Wenn Sie definiert eine Funktion oder Verfahren wie diese, würden Sie eine Stackoverflow

Node NIL(Node a, Node b) { 
    return NIL(NIL(a, b), NIL(a, b)); 
} 
20

Der Schlüssel bekommen under d, warum es keine unendliche Initialisierung verursacht, ist, dass, wenn die Klasse Node initialisiert wird, die JVM es verfolgt und vermeidet Reinitialisierung während einer rekursiven Referenz auf die Klasse innerhalb seiner ursprünglichen Initialisierung. Dies wird in this section of the language spec beschrieben:

Da der Java-Programmiersprache multithreaded, Initialisierung einer Klasse oder Schnittstelle erfordert eine sorgfältige Synchronisation, da einige andere Thread versucht werden können, die gleiche Klasse oder Schnittstelle zur gleichen Zeit zu initialisieren. Es besteht auch die Möglichkeit, dass die Initialisierung einer Klasse oder Schnittstelle rekursiv als Teil der Initialisierung dieser Klasse oder dieser Schnittstelle angefordert wird; Beispielsweise kann ein Variableninitialisierer in Klasse A eine Methode einer nicht verwandten Klasse B aufrufen, die wiederum eine Methode der Klasse A aufrufen kann.Die Implementierung der Java Virtual Machine ist verantwortlich für die Synchronisierung und die rekursive Initialisierung mithilfe der folgenden Prozedur. So

während der statische Initialisierer NIL wird die statische Instanz zu schaffen, die Bezugnahme auf Node.NIL als Teil des Konstruktoraufruf nicht erneut auszuführen wieder den statischen Initialisierer. Stattdessen verweist es nur auf irgendeinen Wert, den die Referenz NIL zu dieser Zeit hat, was in diesem Fall null ist.

Verwandte Themen