JAVA类初始化顺序及final常量初始化顺序
整理下JAVA类初始化顺序和一点点细节~
类加载顺序
结论
验证
父类:
public class Father {
int fatherNbr = 1;
static int fatherStaticNbr = 2;
static {
System.err.println("---static father block start---");
System.err.println("fatherStaticNbr=" + fatherStaticNbr);
System.err.println("---static father block end---");
System.err.println();
}
{
System.err.println("---father block start---");
System.err.println("fatherNbr=" + fatherNbr);
System.err.println("fatherStaticNbr=" + fatherStaticNbr);
System.err.println("---father block end---");
System.err.println();
}
public Father() {
System.err.println("---father init start---");
System.err.println("fatherNbr=" + fatherNbr);
System.err.println("fatherStaticNbr=" + fatherStaticNbr);
System.err.println("---father init end---");
System.err.println();
}
}
子类:
public class Son extends Father {
int sonNbr = 100;
static int sonStaticNbr = 200;
static {
System.err.println("---static son block start---");
System.err.println("sonStaticNbr=" + sonStaticNbr);
System.err.println("---static son block end---");
System.err.println();
}
{
System.err.println("---son block start---");
System.err.println("sonNbr=" + sonNbr);
System.err.println("sonStaticNbr=" + sonStaticNbr);
System.err.println("---son block end---");
System.err.println();
}
public Son() {
System.err.println("---son init start---");
System.err.println("sonNbr=" + sonNbr);
System.err.println("sonStaticNbr=" + sonStaticNbr);
System.err.println("---son init end---");
System.err.println();
}
} System.err.println();
}
}
main:
public class Test {
public static void main(String[] args) {
new Son();
}
}
执行结果:
---static father block start---
fatherStaticNbr=2
---static father block end---
---static son block start---
sonStaticNbr=200
---static son block end---
---father block start---
fatherNbr=1
fatherStaticNbr=2
---father block end---
---father init start---
fatherNbr=1
fatherStaticNbr=2
---father init end---
---son block start---
sonNbr=100
sonStaticNbr=200
---son block end---
---son init start---
sonNbr=100
sonStaticNbr=200
---son init end---
Process finished with exit code 0
由此可见,执行顺序为:
父类static部分 -> 子类static部分
-> 父类非静态变量及非静态代码块 -> 父类构造器
-> 子类非静态变量及非静态代码块 -> 子类构造器
需要注意
- 静态代码块不能调用非静态变量
- 静态变量和静态代码块之间是按定义的先后顺序执行的,所以不能调用在本静态代码块之后才定义的静态变量
即以下两种场景都会报错:
int fatherNbr = 1;
static {
// 非静态变量在静态块之后初始化,无法从静态上下文中引用非静态变量
System.err.println("fatherNbr=" + fatherNbr);
// 调用的静态变量在后面才定义,非法前向引用
System.err.println("fatherStaticNbrAfter=" + fatherStaticNbrAfter);
}
static int fatherStaticNbrAfter = 3;
static在构造器之后执行的场景
若先声明静态对象,仍会调用构造器,再执行其他静态代码块:
public class SpecialTest {
// 先调用了构造方法
static SpecialTest test = new SpecialTest();
static {
System.err.println("static block");
}
{
System.err.println("block");
}
int num = 100;
static int staticNbr = 200; // 第一次执行时,未赋值
static final int staticFinalNbr = 300;
SpecialTest() {
System.err.println("-----init start----");
System.err.println("num=" + num + " staticNbr=" + staticNbr + " staticFinalNbr=" + staticFinalNbr);
System.err.println("-----init end----");
}
public static void main(String[] args) {
new SpecialTest();
}
}
执行结果:
block
-----init start----
num=100 staticNbr=0 staticFinalNbr=300
-----init end----
static block
block
-----init start----
num=100 staticNbr=200 staticFinalNbr=300
-----init end----
可以观察到,首先执行第一行static SpecialTest test = new SpecialTest();
时,调用了构造方法,所以接下来执行的是构造方法,而不是其他静态赋值和静态代码块。
在第一次执行构造方法(static调用)前,会执行非静态代码的赋值和非静态代码块,和final修饰的变量。然后再第一次执行构造方法,由于静态变量赋值未执行,所以打印int的默认值staticNbr=0
。之后才继续执行后续的静态代码块和静态变量赋值。
在第二次执行构造方法时(main方法内调用),已执行的静态代码块不会重复执行,非静态代码块会再次执行,然后现在的构造方法才正常打印所有的变量值。
final初始化顺序
在上面的例子可以看到静态常量static final int staticFinalNbr = 300;
在第一次执行构造方法时也已经赋值完成。这是因为final对象已经加入常量池。