为什么此语句不引发StackOverflowError?


74

我刚刚在另一个问题中看到了这段奇怪的代码。我以为会导致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引用本身。

我不知道为什么不这样做。


7
可能是因为static但我不确定
XtremeBaumer

28
我期望的是,该NIL字段是按声明为new Node(null, null)构造的,因为调用构造函数时,Node.NIL尚未将其设置为任何值。
khelwood

@khelwood是,根据答案,我理解了相同的想法。
安东尼·雷蒙德

4
请切勿在生产代码中使用此代码。它可以提供很好的琐事,但我认为这是故意的混淆。

@chi我不确定,但是我在另一个问题上看到了这段代码,我对它如何工作感到困惑。
安东尼·雷蒙德

Answers:


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已创建的of ,这将创建一个新Node实例,其创建将初始化另一个NIL实例变量,从而导致以结尾的无限个构造函数调用链StackOverflowError


谢谢,您说得更清楚,它的工作方式对我来说仍然很奇怪。但是至少我理解为什么它会这样工作。
安东尼·雷蒙德

5
如果NIL是实例变量,则不会编译为error Cannot reference a field before it is defined
Florian Genser

@FlorianGenser好点。我在注意到Node.NIL传递给构造函数之前写了这部分。
伊兰(Eran)

3
实际上,java.awt.Color很好地演示了静态变量的作用。它有很多不同的颜色,例如Color.BLUE,它也包含对所有其他颜色的引用...当我第一次使用Java时,这使我眼花azz乱。
sfdcfox

感谢@sfdcfox,我将对此进行介绍。
安东尼·雷蒙德

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

20

理解为什么它不会引起无限初始化的关键在于,当Node初始化类时,JVM会跟踪它,并在其原始初始化过程中递归引用该类时避免重新初始化。语言规范的这一部分对此进行了详细说明

因为Java编程语言是多线程的,所以类或接口的初始化需要仔细的同步,因为某些其他线程可能正在尝试同时初始化同一类或接口。也有可能递归地请求类或接口的初始化,作为该类或接口的初始化的一部分;例如,类A中的变量初始值设定项可能会调用不相关的类B的方法,而后者又可能会调用类A的方法。Java虚拟机的实现负责通过使用遵循以下步骤。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.