为什么这个语句没有抛出StackOverflowError?

Ant*_*ond 73 java stack-overflow

我刚刚在另一个问题中看到了这段奇怪的代码.我认为这会导致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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为它会爆炸,因为Node.NIL引用它自己构建.

我无法弄明白为什么它没有.

Era*_*ran 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 Nodecreated,它将创建一个新Node实例,其创建将初始化另一个NIL实例变量,从而导致无限的构造函数调用链结束StackOverflowError.

  • 如果`NIL`是一个实例变量,它将不会编译错误"在定义之前不能引用字段". (5认同)
  • java.awt.Color实际上是静态变量的一个很好的演示.它有一堆不同的颜色,比如Color.BLUE,它还包含对所有其他颜色的引用......当我第一次使用Java时,这让我眼花缭乱. (3认同)

Pet*_*rey 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
    }
}
Run Code Online (Sandbox Code Playgroud)

这与写作没什么不同

NIL = null; // set implicitly
NIL = new Node(NIL, NIL);
Run Code Online (Sandbox Code Playgroud)

如果你定义了这样的函数方法,你会得到一个StackoverflowException

Node NIL(Node a, Node b) {
    return NIL(NIL(a, b), NIL(a, b));
}
Run Code Online (Sandbox Code Playgroud)


man*_*uti 20

理解为什么它不会导致无限初始化的关键在于,当Node初始化类时,JVM会跟踪它并避免在其原始初始化内对类的递归引用期间重新初始化.这在语言规范的这一节中有详细说明:

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

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