【问题标题】:Why doesn't this statement throw a StackOverflowError?为什么这个语句不抛出 StackOverflowError?
【发布时间】:2017-01-30 09:37:50
【问题描述】:

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

我不知道为什么它没有。

【问题讨论】:

  • 可能是因为static 但我不确定
  • 我所期望的是NIL 字段被构造为它被声明为new Node(null, null),因为当调用构造函数时,Node.NIL 尚未设置为任何内容。
  • @khelwood 是的,根据答案,我理解同样的想法。
  • 请不要在生产代码中使用它。它可以提供一个很好的琐事,但我认为这是故意混淆。
  • @chi 我不确定,但我在另一个问题上看到了这段代码,我对它如何工作感到困惑。

标签: java stack-overflow


【解决方案1】:

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 实例变量,从而导致无限链的构造函数调用以@987654335 结束@。

【讨论】:

  • 谢谢,你说得更清楚了,它的工作方式对我来说仍然很奇怪。但至少我明白它为什么会这样工作。
  • 如果NIL 是一个实例变量,它将无法编译并出现错误Cannot reference a field before it is defined
  • @FlorianGenser 好点。在注意到 Node.NIL 被传递给构造函数之前,我写了这部分。
  • java.awt.Color 实际上很好地演示了静态变量的作用。它有一堆不同的颜色,比如 Color.BLUE,它还包含对所有其他颜色的引用……这让我在第一次开始使用 Java 时感到眼花缭乱。
  • 感谢@sfdcfox,我会看看这个。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-24
      • 2020-07-16
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 2011-03-25
      • 2014-07-09
      相关资源
      最近更新 更多