为什么这个语句不抛出 StackOverflowError?
Why doesn't this statement throw a StackOverflowError?
我刚刚在另一个问题中看到这段 奇怪的 代码。我认为这会导致 WhosebugError
被抛出,但它不会...
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
引用自己来构建。
我不明白为什么没有。
NIL
是一个静态变量。当 class 被初始化时,它被初始化一次。初始化时,会创建一个 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
实例变量,导致到以 WhosebugError
.
结尾的无限构造函数调用链
变量 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);
如果你像这样定义了一个函数或方法,你会得到一个WhosebugException
Node NIL(Node a, Node b) {
return NIL(NIL(a, b), NIL(a, b));
}
理解为什么它不会导致无限初始化的关键是当 class Node
被初始化时,JVM 会跟踪它并 避免 re-initialization 在其原始初始化中递归引用 class 期间。这在 this section of the language spec:
中有详细说明
Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure.
因此,当静态初始化程序创建静态实例 NIL
时,作为构造函数调用的一部分对 Node.NIL
的引用不会再次 re-execute 静态初始化程序。相反,它只是引用当时引用 NIL
的任何值,在本例中为 null
。
我刚刚在另一个问题中看到这段 奇怪的 代码。我认为这会导致 WhosebugError
被抛出,但它不会...
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
引用自己来构建。
我不明白为什么没有。
NIL
是一个静态变量。当 class 被初始化时,它被初始化一次。初始化时,会创建一个 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
实例变量,导致到以 WhosebugError
.
变量 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);
如果你像这样定义了一个函数或方法,你会得到一个WhosebugException
Node NIL(Node a, Node b) {
return NIL(NIL(a, b), NIL(a, b));
}
理解为什么它不会导致无限初始化的关键是当 class Node
被初始化时,JVM 会跟踪它并 避免 re-initialization 在其原始初始化中递归引用 class 期间。这在 this section of the language spec:
Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure.
因此,当静态初始化程序创建静态实例 NIL
时,作为构造函数调用的一部分对 Node.NIL
的引用不会再次 re-execute 静态初始化程序。相反,它只是引用当时引用 NIL
的任何值,在本例中为 null
。