Java类中数据域初始化顺序
在《Java核心技术:卷一》中,对于初始化数据域的初始化次序,有以下说明:
- 所有数据域都被初始化为默认值
- 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
- 执行这个构造器的主体。
对于静态域的初始化过程: - 在类的第一次加载时,将静态域初始化为默认值
- 所有的静态初始化语句以及静态初始化块都依照类定义的顺序执行。
然而,Java类中数据域为什么是按照上面所说的步骤进行的呢?以下从JVM的角度来分析这个问题。
首先看静态域的初始化过程:
public class Demo{
static int i=10;
static{
i=20;
}
static{
i=30;
}
}
我们将上面代码的字节码反编译后得到以下结果,会发现编译器会按上至下,收集所有static静态代码和静态成员赋值的代码,合并成一个特殊的方法()V
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 10
2: putstatic #2 // Field i:I
5: bipush 20
7: putstatic #2 // Field i:I
10: bipush 30
12: putstatic #2 // Field i:I
15: return
LineNumberTable:
line 2: 0
line 5: 5
line 8: 10
line 9: 15
}
接下来我们看非静态变量初始化过程:
public class Demo{
private String a=“s1”;
{
b=20;
}
private int b=20;
{
a=“s2”;
}
public Demo(String a, int b){
this.a=a;
this.b=b;
}
public static void main(String[] args) {
Demo d=new Demo(“s3”, 30);
System.out.println(d.a);
System.out.println(d.b);
}
}
上面代码的字节码经过反编译后,得到以下结果,我们会发现编译器按从上到下的顺序,收集所有代码和成员变量复制的代码,形成新的构造方法,但原始构造方法会被放在最后,如下图28-35的内容
public Demo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String s1
7: putfield #3 // Field a:Ljava/lang/String;
10: aload_0
11: bipush 20
13: putfield #4 // Field b:I
16: aload_0
17: bipush 20
19: putfield #4 // Field b:I
22: aload_0
23: ldc #5 // String s2
25: putfield #3 // Field a:Ljava/lang/String;
28: aload_0 //-----------------------------------
29: aload_1
30: putfield #3 // Field a:Ljava/lang/String;
33: aload_0
34: iload_2
35: putfield #4 // Field b:I-------------------------
38: return