Java数据初始化顺序:
注:本文只描述涉及到继承的初始化顺序,如果想了解不涉及继承的:请点此处。
类的初始化顺序
1.当类的.class文件被载入后,有关静态初始化的所有动作都会执行,而且只执行一次(注意:初始化是有顺序的,一般顺序为代码中定义的顺序)
2.当使用new关键字创建类的实例的时候,首先会在堆上为对象分配足够的存储空间。
3.划分出存储空间之后,这块存储空间会被清零,这一步就自动地为对象中所有的基本数据类型的变量都设置成了默认值。
4.执行所有出现于字段定义处的初始化动作。
5.执行构造器,即构造方法。
首先解释一下上面的第4条,其中“出现于定义处的初始化动作”是指int i
而不是int i = 4
。在语句int i = 4
中实际上是执行了int i
和i= 4
这两条语句而不涉及继承时,可以简单的认为两者是同时发生的,但是涉及继承时,就不能这么认为了,在类的初始化顺序中的第四条其实仅仅只是执行了int i
而没有执行i = 4
,而i= 4
这句话是放在了构造器内部执行的。
定义处的初始化
现在我们通过代码来证明一下上述的结论:
代码
public class Init {
Assist t1 = new Assist(1);
int t2 = 10;
Init(){
System.out.println("父类构造器调用");
display();
}
public void display(){
System.out.println("t2 = "+t2);
System.out.println(t1);
}
}
class Son extends Init{
Assist t1 = new Assist(2);
int t2 = 2;
Son(){
System.out.println("子类构造器调用");
}
@Override
public void display(){
System.out.println("t2 = "+t2);
System.out.println(t1);
}
public static void main(String args[]) {
new Son();
}
}
class Assist{
String name = "我是在这里被调用的";
Assist(int i){
System.out.println(name+" "+i);
}
@Override
public String toString(){
return name;
}
}
运行结果
我是在这里被调用的 1
父类构造器调用
t2 = 0
null
我是在这里被调用的 2
子类构造器调用
结果分析
我们先假设类的初始化顺序中的第4步是执行int i = 4
所有的步骤,即在构造器调用前就完成对变量的赋值。
在本例中,根据继承初始化的顺序(必须先创建父类,才能创建子类),在创建子类的前,会先创建父类,而在父类的构造器中调用了display()方法,对于方法的重写的特性,父类构造器中调用的display()方法仍然为子类的display()方法,而在子类的display()方法中我们打印了子类的成员变量 t1和t2,根据输出情况可以看到分别是”null”和”0”,而我们在假设中假定int i = 4;
是同时执行的,于实验结果不符,
所以得出结论int i = 4;
应该被拆分成int i
和i = 4
,而第一句是在构造器调用前执行的,即类初始化顺序中的第4步;而i= 4
应该是在构造器内部执行。
继承中类的初始化
涉及继承时父类和子类的初始化顺序会有一点小小的变化,不过基本都遵守类的初始化顺序。下面我们通过代码来看一看这小小的不同:
代码
public class Init {
Init(){
System.out.println("父类构造器调用");
}
{
System.out.println("父类初始化子句调用");
}
static{
System.out.println("父类静态块调用");
}
}
class Son extends Init{
Son(){
System.out.println("子类构造器调用");
}
{
j = 10;
System.out.println("子类初始化子句调用");
}
int j;
static{
i = 20;
System.out.println("子类静态块调用");
}
static int i;
public void display(){
System.out.println("i = "+i);
System.out.println("j = "+j);
}
public static void main(String args[]) {
Son son = new Son();
son.display();
}
}
运行结果
父类静态块调用
子类静态块调用
父类初始化子句调用
父类构造器调用
子类初始化子句调用
子类构造器调用
i = 20
j = 10
结果分析
在上一篇博客里我们得出的结论是:静态块、静态方法、静态变量的初始化顺序是最高的。在涉及继承的时候该结论同样适用,当子类被加载时,编译器检测到extends关键字,于是先去完成父类中的静态的事物的初始化,完成后回到子类来初始化子类中的静态域的初始化(保证静态的事物初始化顺序最高,即使是父类的初始化子句),接着执行子类的中的静态方法(main方法),调用子类构造器,然后调用父类构造器,调用前检测到父类中存在初始化子句,于是先执行初始化子句,然后调用构造器,完成后调用子类构造器,与父类相同,调用前检测到子类中有初始化子句等等。最后父、子类构造器调用完成,初始化结束。
总结
通过上述实例,我们总结一下完整的初始化顺序:父类静态块、静态变量、静态方法>>子类静态块、静态变量、静态方法>>父类初始化子句、变量定义>>父类构造器>>子类初始化子句、变量定义>>子类构造器!