Why doesn't this statement throw a StackOverflowError?
Solution 1:
NIL
is a static variable. It is initialized one time, when the class is initialized. When it is initialized, a single Node
instance is created. The creation of that Node
doesn't trigger creation of any other Node
instances, so there is not infinite chain of calls. Passing Node.NIL
to the constructor call has the same effect as passing null
, since Node.NIL
is not yet initialized when the constructor is called. Therefore public static Node NIL = new Node(Node.NIL, Node.NIL);
is the same as public static Node NIL = new Node(null, null);
.
If, on the other hand, NIL
was an instance variable (and wasn't passed as an argument to the Node
constructor, since the compiler would have prevented you from passing it to the constructor in that case), it would be initialized every time an instance of Node
was created, which would create a new Node
instance, whose creation would initialize another NIL
instance variable, leading to infinite chain of constructor calls that would end in StackOverflowError
.
Solution 2:
The variable NIL is first given the value null
and then initialised once top to bottom. It isn't a function and isn't defined recursively. Any static field you use before it is initialised has the default value and your code is the same as
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
}
}
This is no different to writing
NIL = null; // set implicitly
NIL = new Node(NIL, NIL);
If you defined a function or method like this, you would get a StackoverflowException
Node NIL(Node a, Node b) {
return NIL(NIL(a, b), NIL(a, b));
}
Solution 3:
The key to understand why it doesn't cause infinite inititialization is that when the class Node
is being initialized, the JVM keeps track of it and avoids re-initialization during a recursive reference to the class within its original initialization. This is detailed in this section of the language spec:
Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure.
So while the static initializer is creating the static instance NIL
, the reference to Node.NIL
as part of the constructor call does not re-execute the static initializer again. Instead it just references whatever value the reference NIL
has at that time, which is null
in this case.