从非静态上下文调用相同 class 的 java 构造函数会导致递归,但是对于静态它可以正常工作吗?

calling a java constructor of same class from non static context leads to recursion but with static it works fine?

我想了解 Java class 的初始化顺序。具体什么时候以什么顺序是static和Instance initializer/fields执行的。我想出了这个 中的例子。为什么将静态添加到自构造函数调用会阻止代码进入递归。

public class Test {
    public static void main(String a[]) {
        Cons1 c1 = new Cons1();
    }
}

class Cons1 {
    static Cons1 c = new Cons1(); /* if static is removed then recursion 
                                    occurs */
    Cons1() {
         //does something
    }
}

静态上下文和实例上下文之间的这种行为差异是否有任何特定原因。我浏览了 Java 文档 Detailed Initialization Procedure,但无法理解这种行为背后的逻辑。对 JLS 规范的任何解释或参考都会有所帮助。

PS : 我已经完成了这个 Similar Whosebug post,但是我无法从那里得到答案。

If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. A static field, sometimes called a class variable, is incarnated when the class is initialized (§12.4).

JLS 10 - 8.3.1.1. static Fields

另一方面,

If the declarator is for an instance variable (that is, a field that is not static), then the following rules apply to its initializer:

  • At runtime, the initializer is evaluated and the assignment performed each time an instance of the class is created.

JLS 10 - 8.3.2. Field Initialization


加个println语句,看全貌:

class Cons1 {
    static Cons1 c = new Cons1();

    Cons1() {
        System.out.println("the constructor was called");
    }

    public static void main(String[] args) {
        Cons1 c1 = new Cons1();
        Cons1 c2 = new Cons1();
    }
}

它输出了三次“构造函数被调用”:

1 - 当加载 class 并且初始化静态字段 c 时;
2 - 创建 c1 时;
3 - 创建 c2 时。

现在我们将其与带有实例字段的示例进行比较:

class Cons1 {
    Cons1 c = new Cons1();

    Cons1() {
        System.out.println("the constructor was called");
    }

    public static void main(String[] args) {
        Cons1 c1 = new Cons1();
    }
}

显然,它失败了,下一个堆栈跟踪什么也没打印:

Exception in thread "main" java.lang.WhosebugError
    at Cons1.<init>(Cons1.java:33)
    ...
    at Cons1.<init>(Cons1.java:33)

原因是 Cons1 的每个实例都需要另一个 Cons1 对象。所以我们溢出了调用堆栈,将 Cons1.<init> 方法推入其中。结果,当堆栈达到其最大允许大小时,我们以异常结束。