为什么静态字段没有及时初始化?

2022-08-31 19:52:45

以下代码打印一次。null

class MyClass {
   private static MyClass myClass = new MyClass();
   private static final Object obj = new Object();
   public MyClass() {
      System.out.println(obj);
   }
   public static void main(String[] args) {}
}

为什么在构造函数运行之前未初始化静态对象?

更新

我只是在没有注意的情况下复制了这个示例程序,我以为我们谈论的是2个对象字段,现在我看到第一个是MyClass字段。:/


答案 1

因为静态是按照它们在源代码中给出的顺序初始化的。

看看这个:

class MyClass {
  private static MyClass myClass = new MyClass();
  private static MyClass myClass2 = new MyClass();
  public MyClass() {
    System.out.println(myClass);
    System.out.println(myClass2);
  }
}

这将打印:

null
null
myClassObject
null

编辑

好吧,让我们把它画出来,以便更清楚一点。

  1. 静态按源代码中声明的顺序逐个初始化。
  2. 由于第一个静态字段在其余静态字段之前初始化,因此在其初始化期间,其余静态字段为 null 或默认值。
  3. 在第二个静态的启动期间,第一个静态是正确的,但其余的仍然为空或默认值。

这很清楚吗?

编辑 2

正如Varman所指出的那样,在初始化时,对自身的引用将为空。如果你仔细想想,这是有道理的。


答案 2

让我们尝试一种不同的方法来解释这一点...

这是 JVM 首次引用类时所经历的序列。MyClass

  1. 将字节码加载到内存中。
  2. 清除静态存储的内存(二进制零)。
  3. 初始化类:
    1. 按每个静态初始值设定项出现的顺序执行它,这包括静态变量和块。static { ... }
    2. 然后,JVM 将静态变量初始化为 的新实例。myClassMyClass
    3. 发生这种情况时,JVM 会注意到已加载(字节码)并且正在初始化,因此它跳过初始化。MyClass
    4. 在堆上为对象分配内存。
    5. 执行构造函数。
    6. 打印出其值仍然是(因为它不是堆和构造函数初始化变量的一部分)。objnull
    7. 构造函数完成后,执行下一个静态初始值设定项,该初始值设定项设置为 的新实例。objObject
  4. 类初始化完成。从这一点开始,所有构造函数调用都将按照您的假设/期望运行 - 这不会只是对实例的引用。objnullObject

请记住,Java 指定为变量分配一次值。这并不是说在代码引用它时会为它赋值,除非您确保代码在赋值后引用它。final

这不是一个错误。这是在类自己的初始化期间处理类用法的已定义方法。如果不是这样,那么JVM将进入无限循环。请参阅步骤 #3.3(如果 JVM 不跳过正在初始化的类的初始化,它只会继续初始化它 - 无限循环)。

另请注意,这一切都发生在首次引用该类的同一线程上。其次,JVM 保证在允许任何其他线程使用此类之前完成初始化。