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

Java 为什么静态字段没有及时初始化?,java,static,null,nullpointerexception,Java,Static,Null,Nullpointerexception,以下代码打印一次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) {} } 为什么在构造函数运行之前静

以下代码打印一次
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) {}
}
为什么在构造函数运行之前静态对象没有初始化

更新


我刚才没有注意就复制了这个示例程序,我以为我们讨论的是两个对象字段,现在我看到第一个是MyClass字段..:/

因为静态是按照源代码中给定的顺序初始化的

看看这个:

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
编辑

好的,让我们把这个画出来,更清楚一点

  • 静态将按照源代码中声明的顺序逐个初始化
  • 由于第一个静态字段是在其余字段之前初始化的,因此在初始化过程中,其余静态字段为null或默认值
  • 在启动第二个静态变量的过程中,第一个静态变量是正确的,但其余变量仍然为null或默认值
  • 清楚了吗

    编辑2


    正如Varman指出的,在初始化时对自身的引用将为null。仔细想想,这很有意义。

    这是因为静态字段的初始化顺序与它们定义的顺序相同。

    这是因为Java按照声明的顺序执行静态部分。在您的例子中,顺序是

  • 新MyClass
  • 新对象
  • 当执行#1时,obj仍然没有初始化,因此它打印null。尝试以下方法,您将看到不同之处:

    class MyClass {
      private static final Object obj = new Object();
      private static MyClass myClass = new MyClass();
      public MyClass() {
        System.out.println(obj); // will print null once
      }
    }
    
    一般来说,最好同时避免这种构造。如果您试图创建一个单例,那么代码片段应该是这样的:

    class MyClass {
    
      private static final MyClass myClass = new MyClass();
    
      private Object obj = new Object();
    
      private MyClass() {
        System.out.println(obj); // will print null once
      }
    }
    

    让我们用另一种方式来解释这个

    这是JVM第一次引用类
    MyClass
    时所经历的序列

  • 将字节码加载到内存中
  • 静态存储器的内存被清除(二进制零)
  • 初始化类:
  • 按照出现的顺序执行每个静态初始值设定项,这包括静态变量和
    static{…}
  • JVM然后将
    myClass
    静态变量初始化为
    myClass
    的新实例
  • 发生这种情况时,JVM注意到
    MyClass
    已经加载(字节码)并且正在初始化过程中,因此它跳过初始化
  • 为对象在堆上分配内存
  • 执行构造函数
  • 打印出
    obj
    的值,该值仍然是
    null
    (因为它不是堆和构造函数初始化变量的一部分)
  • 构造函数完成后,执行下一个静态初始值设定项,将
    obj
    设置为
    对象的新实例
  • 类初始化完成。从这一点来看,所有构造函数调用的行为都将与您假定/预期的一样-即
    obj
    将不是
    null
    ,而是对
    对象
    实例的引用
  • 请记住,Java指定一个
    final
    变量只分配一次值。除非您确保代码在赋值后引用它,否则不能保证在代码引用它时为其赋值

    这不是一个bug。这是在类自身初始化期间处理类使用情况的定义方式。如果不是这样,那么JVM将进入无限循环。请参阅步骤#3.3(如果JVM没有跳过正在初始化的类的初始化,它将继续初始化它-无限循环)

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

    @pyrolistic

    由于第一个静态字段myclass的初始值没有完全构造,所以我得到的结果是

    空的 无效的 证明。MyObject@70f9f9d8
    null

    。因为
    myClass
    本身是静态的。好的,静态是按顺序初始化的。并且静态在构造函数之前,那么为什么在构造函数运行时不初始化它呢?对我来说真的像个虫子。@Tom,不,你搞错了。静态不在构造函数之前。当分别调用构造函数时,静态是init。在我的示例中,当第一个静态变量是带有MyClass的init时,将调用构造函数。当构造函数运行时,myClass是init(因为它本身正在运行),但myClass2不是。当第二个MyClass为init时,它会再次调用构造函数,而这次MyClass已为init,而myClass2此时为init,所以构造函数运行时,它的类似静态在另一个线程中初始化?你有详细解释这一点的相同文本的链接吗?@Pyrolistical:当我执行你的程序时,我得到了不同的结果。它打印了关于执行顺序的
    null-null myClassObjectref-null
    Correct。但是,单例并不是真正的单例,因为构造函数是公共的。在构造函数运行之前声明并要求初始化静态,那么为什么在构造函数运行时不初始化它呢?对我来说真的很像一个bug。@Tom您必须通过在静态中调用
    new MyClass()
    来理解,您正在调用构造函数,我们在这里遇到了通信故障。我实际上知道new MyClass()是对构造函数的调用,但这并不能解释为什么构造函数运行时静态字段为空。与在构造函数之前初始化实例字段一样,静态字段应为。。但是为什么不呢?静态成员是在实例构造函数运行之前构造的。只是在代码中,静态初始值设定项也会调用构造函数。这是一个鸡和蛋的问题。这并不能回答为什么它在构造函数时是空的。答案很好,Kevin,这里最好的一个,我想你的意思是它跳过了“静态”初始化。