为什么此语句不抛出 StackOverflow 错误?

我刚刚在另一个问题中看到了这段 真奇怪代码。我以为这会导致 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引用它自己来构建。

我不明白为什么没有。

7051 次浏览

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结束。

变量 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);

如果像这样定义 功能方法,就会得到 Stackoverflow 异常

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

要理解它为什么不会导致无限初始化的关键是,当类 Node被初始化时,JVM 会跟踪它,并且在原始初始化过程中对类的递归引用期间,避免会重新初始化。这一点详见 语言规范的这一部分:

因为 Java 编程语言是多线程的,所以类或接口的初始化需要仔细的同步,因为其他线程可能试图在同一时间初始化相同的类或接口。例如,类 A 中的变量初始化器可能调用一个不相关类 B 的方法,而这个方法又可能调用类 A 的方法。Java 虚拟机的实现通过使用以下过程负责同步和递归初始化。

因此,当静态初始化器创建静态实例 NIL时,作为构造函数调用的一部分对 Node.NIL的引用不会再次重新执行静态初始化器。相反,它只是引用当时引用 NIL的任何值,在本例中就是 null