为什么这条语句不会引发StackOverflowError?

时间:2017-01-30 09:37:50

标签: java stack-overflow

我刚刚在另一个问题中看到了这段奇怪的代码。我认为它会导致std::copy被抛出,但它不会......

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; } } 引用自身来构建。

我无法弄明白为什么没有。

3 个答案:

答案 0 :(得分: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结尾。

答案 1 :(得分: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));
}

答案 2 :(得分:20)

理解它为什么不会导致无限初始化的关键是,当初始化类Node时,JVM会跟踪它并且避免在重新初始化期间重新初始化在其原始初始化内对类的递归引用。这在this section of the language spec

中有详细说明
  

由于Java编程语言是多线程的,因此初始化类或接口需要仔细同步,因为其他一些线程可能正在尝试同时初始化同一个类或接口。 作为该类或接口的初始化的一部分,还可以递归地请求类或接口的初始化;例如,类A中的变量初始化程序可能会调用不相关的类B的方法,而该方法又可能调用类A的方法。通过使用该方法,Java虚拟机的实现负责处理同步和递归初始化。以下程序。

因此,当静态初始化程序创建静态实例NIL时,作为构造函数调用的一部分对Node.NIL的引用不会再次重新执行静态初始化程序。相反,它只引用参考NIL当时具有的任何值,在这种情况下为null