为什么字段在构造函数之前似乎已经被初始化了?

23
public class Dog {

 public static Dog dog = new Dog();
 static final int val1 = -5;
 static int val2 = 3;
 public int val3;

 public Dog() {
      val3 = val1 + val2;
 }

public static void main(String[] args) {
    System.out.println(Dog.dog.val3);
}
}

输出结果为-5

从这个结果来看,似乎val2的初始化在dog成员和实例化完成之前。

为什么会出现这样的顺序?


1
可能会先声明 final 字段(也许是因为它们在 Java 字节码中存储方式不同),然后按照编写顺序声明非 final 字段。如果是这样,那么在声明 Dog 实例时,val1 的值将为 -5,但 val2 仍设置为默认值 0。 - Sirac
3
因为在类加载时,final变量是首先被加载的,然后按照声明顺序加载其余静态变量。更多细节请参考https://dev59.com/vGcs5IYBdhLWcg3w-YhD - Adi
6
"final"关键字将int类型变量声明为"编译时常量",不可修改。 - TheLostMind
5个回答

21

如果您最终移动了狗的实例,您可能会发现输出变为-2。

public class Dog {

     static final int val1 = -5;// This is final, so will be initialized at compile time 
     static int val2 = 3;
     public int val3;

     public static Dog dog = new Dog();//move to here
 
     public Dog() {
          val3 = val1 + val2;
     }

    public static void main(String[] args) {
        System.out.println(Dog.dog.val3);//output will be -2
    }
}

最终字段(其值为编译时常量表达式)将首先被初始化,然后其余字段将按文本顺序执行。

因此,在您的情况下,当dog实例被初始化时,static int val2(0)尚未初始化,而static final int val1(-5)则已经初始化了,因为它是final。

http://docs.oracle.com/javase/specs/jls/se5.0/html/execution.html#12.4.2指出:

按文本顺序执行类变量初始化器和静态初始化器或接口的字段初始化器,好像它们是单个块一样除了编译时常量的最终类变量和接口字段将首先被初始化


更新到较新的版本

这里是jdk7版本的http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2

最终字段在第6步:

然后,初始化最终类变量和接口字段,它们的值是编译时常量表达式

而静态字段在第9步:

接下来,按文本顺序执行类变量初始化器和静态初始化器或接口的字段初始化器,好像它们是单个块一样。


谢谢,你提供了我期望的官方参考资料。 - Chao

10

变量声明顺序。因为它是一个常量,所以首先初始化static final int val1。然而,在实例化public static Dog dog = new Dog();时,static int val2仍然是0


6
发生了什么事情…
正在执行的第一行是 public static Dog dog = new Dog();。现在,必须要记住两件事情。
  1. final int 使其成为一个编译时常量。因此,-5 已经被硬编码到你的代码中。

  2. 调用 new Dog() 并调用构造函数将值设置为 0 + -5 = -5

val2 更改为 final,你会看到不同之处(你将得到答案 -2)。

注意:静态字段按照遇到的顺序进行初始化。


4

在你的测试中的初始化顺序如下:

  1. static final int val1 = -5; //常量作为静态变量
  2. public static Dog dog = new Dog(); //然后' dog '被初始化,但它的成员变量val2没有被初始化
  3. static int val2 = 3; //最后' val2 '被初始化

这个代码更改将输出-2;

public class Dog {

     //public static Dog dog = new Dog();
     static final int val1 = -5;
     static int val2 = 3;
     public int val3;
     public static Dog dog = new Dog();     //moved here

     public Dog() {
          val3 = val1 + val2;
     }


    public static void main(String[] args) {
        System.out.println(Dog.dog.val3);

    }
}

4

所有静态变量都在单独的静态构造函数中初始化,在类加载时执行。按它们在代码中出现的顺序进行初始化。你的示例编译成类似于以下内容:

public class Dog {

 public static Dog dog;
 static final int val1 = -5;
 static int val2;
 public int val3;

 static {
  dog = new Dog();
  val2 = 3;
 }

 public Dog() {
      val3 = val1 + val2;
 }

 public static void main(String[] args) {
     System.out.println(Dog.dog.val3);
 }
}

因此,类/实例变量的顺序非常重要。类构造函数执行发生在初始化的最后。常量是在这之前解析的。更多信息请参见创建新类实例

你的示例很好地解释了我的程序,但我想知道关于你的论点“所有静态变量都在单独的静态构造函数中初始化”的官方解释是否存在。 - Chao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接