类初始化和实例初始化
本文主要记录 Java 中类和实例初始化顺序问题。
代码示例
Father:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class Father {
private int i = test();
private static int j = method();
static {
System.out.println("(1)");
}
Father() {
System.out.println("(2)");
}
{
System.out.println("(3)");
}
public int test() {
System.out.println("(4)");
return 1;
}
public static int method() {
System.out.println("(5)");
return 1;
}
}
Son:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.println("(6)");
}
Son() {
System.out.println("(7)");
}
{
System.out.println("(8)");
}
public int test() {
System.out.println("(9)");
return 1;
}
public static int method() {
System.out.println("(10)");
return 1;
}
public static void main(String[] args) {
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
}
输出结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17(5)
(1)
(10)
(6)
(9)
(3)
(2)
(9)
(8)
(7)
(9)
(3)
(2)
(9)
(8)
(7)
在 Java 中,类初始化(静态层面)和实例初始化(对象层面)的执行顺序是面试和开发中的核心考点,其本质遵循「静态优先、父类优先、变量初始化与代码块按书写顺序」的核心规则。以下是系统化的总结,包含所有关键初始化场景的执行顺序及示例验证。
核心分类与基础规则
先明确几个关键概念的定义,避免混淆:
| 类型 | 包含内容 | 触发时机 |
|---|---|---|
| 类初始化(静态初始化) | 静态变量(类变量)初始化、静态代码块(static{}) | 类首次加载时(仅执行 1 次) |
| 实例初始化 | 成员变量(实例变量)初始化、实例代码块({})、构造方法 | 每次创建对象(new)时执行 |
完整初始化顺序(终极规则)
整体优先级:父类静态初始化 → 子类静态初始化 → 父类实例初始化 → 子类实例初始化
细分到具体步骤(按执行顺序):
- 父类静态阶段(类加载时执行,仅 1 次)
- 父类静态变量的显式初始化(按声明顺序);
- 父类静态代码块(
static{},按书写顺序)。
- 子类静态阶段(类加载时执行,仅 1 次)
- 子类静态变量的显式初始化(按声明顺序);
- 子类静态代码块(
static{},按书写顺序)。
- 父类实例阶段(每次
new子类 / 父类对象时执行)- 父类成员变量的显式初始化(按声明顺序);
- 父类实例代码块(
{},按书写顺序); - 父类构造方法(若构造方法中调用了
this(),则优先执行对应重载构造方法,再回到当前步骤)。
- 子类实例阶段(每次
new子类对象时执行)- 子类成员变量的显式初始化(按声明顺序);
- 子类实例代码块(
{},按书写顺序); - 子类构造方法(同理,
this()优先)。
关键补充规则
- 变量与代码块的「书写顺序」优先级:
同一阶段内(如父类静态阶段、子类实例阶段),变量初始化和代码块按代码中书写的先后顺序执行,而非 “变量优先” 或 “代码块优先”。示例:1
2
3
4
5
6
7
8
9class A {
// 先执行:静态变量初始化
static int a = 10;
// 后执行:静态代码块
static {
System.out.println(a); // 输出10
a = 20;
}
}
构造方法的隐式调用:子类构造方法第一行默认隐式调用
super()(父类无参构造),因此父类实例初始化一定在子类实例初始化前;若显式调用super(xxx),则优先执行父类对应构造方法。静态变量 / 代码块仅执行一次:无论创建多少个对象,类的静态初始化仅在类首次加载时执行一次(如首次
new对象、访问静态变量 / 方法、反射加载类时)。final 静态常量的特殊情况:
public static final修饰的编译期常量(如static final int a = 10),会在编译阶段直接嵌入到使用处,不会触发类初始化;但如果是运行期常量(如static final int a = new Random().nextInt()),则仍遵循静态初始化顺序。
其它例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53// 父类
class Parent {
// 父类静态变量
static int parentStaticVar = print("父类静态变量初始化");
// 父类静态代码块
static {
System.out.println("父类静态代码块执行");
}
// 父类成员变量
int parentMemberVar = print("父类成员变量初始化");
// 父类实例代码块
{
System.out.println("父类实例代码块执行");
}
// 父类构造方法
public Parent() {
System.out.println("父类构造方法执行");
}
// 辅助打印方法
public static int print(String msg) {
System.out.println(msg);
return 0;
}
}
// 子类
class Child extends Parent {
// 子类静态变量
static int childStaticVar = print("子类静态变量初始化");
// 子类静态代码块
static {
System.out.println("子类静态代码块执行");
}
// 子类成员变量
int childMemberVar = print("子类成员变量初始化");
// 子类实例代码块
{
System.out.println("子类实例代码块执行");
}
// 子类构造方法
public Child() {
// 隐式super(),优先执行父类实例初始化
System.out.println("子类构造方法执行");
}
public static void main(String[] args) {
System.out.println("=====第一次创建子类对象=====");
new Child();
System.out.println("=====第二次创建子类对象=====");
new Child();
}
}
执行结果(关键看顺序):
1 | 父类静态变量初始化 |
结果分析:
- 静态阶段仅执行 1 次(父→子);
- 每次
new对象时,实例阶段按「父成员→父实例块→父构造→子成员→子实例块→子构造」执行; - 同一阶段内,变量和代码块按书写顺序执行。
简化记忆口诀
- 静态先,实例后;
- 父类先,子类后;
- 变量 / 代码块按书写顺序,构造方法最后;
- 静态只一次,实例每次
new。
常见面试题陷阱
问:子类静态代码块和父类构造方法谁先执行?
答:子类静态代码块(静态阶段整体优先于实例阶段)。
问:实例代码块和构造方法的关系?
答:实例代码块是构造方法的 “前置逻辑”,编译后会被插入到构造方法中(
super()之后,构造方法代码之前)。
问:静态变量赋值引用了其他类的静态变量,会触发什么?
答:会先加载并初始化被引用的类(遵循父类静态优先规则)。