类初始化和实例初始化

本文主要记录 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
25
public 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
30
public 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. 父类静态阶段(类加载时执行,仅 1 次)
    • 父类静态变量的显式初始化(按声明顺序);
    • 父类静态代码块(static{},按书写顺序)。
  2. 子类静态阶段(类加载时执行,仅 1 次)
    • 子类静态变量的显式初始化(按声明顺序);
    • 子类静态代码块(static{},按书写顺序)。
  3. 父类实例阶段(每次new子类 / 父类对象时执行)
    • 父类成员变量的显式初始化(按声明顺序);
    • 父类实例代码块({},按书写顺序);
    • 父类构造方法(若构造方法中调用了this(),则优先执行对应重载构造方法,再回到当前步骤)。
  4. 子类实例阶段(每次new子类对象时执行)
    • 子类成员变量的显式初始化(按声明顺序);
    • 子类实例代码块({},按书写顺序);
    • 子类构造方法(同理,this()优先)。

关键补充规则

  1. 变量与代码块的「书写顺序」优先级

同一阶段内(如父类静态阶段、子类实例阶段),变量初始化和代码块按代码中书写的先后顺序执行,而非 “变量优先” 或 “代码块优先”。示例:

1
2
3
4
5
6
7
8
9
class A {
// 先执行:静态变量初始化
static int a = 10;
// 后执行:静态代码块
static {
System.out.println(a); // 输出10
a = 20;
}
}
  1. 构造方法的隐式调用:子类构造方法第一行默认隐式调用super()(父类无参构造),因此父类实例初始化一定在子类实例初始化前;若显式调用super(xxx),则优先执行父类对应构造方法。

  2. 静态变量 / 代码块仅执行一次:无论创建多少个对象,类的静态初始化仅在类首次加载时执行一次(如首次new对象、访问静态变量 / 方法、反射加载类时)。

  3. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
父类静态变量初始化
父类静态代码块执行
子类静态变量初始化
子类静态代码块执行
=====第一次创建子类对象=====
父类成员变量初始化
父类实例代码块执行
父类构造方法执行
子类成员变量初始化
子类实例代码块执行
子类构造方法执行
=====第二次创建子类对象=====
父类成员变量初始化
父类实例代码块执行
父类构造方法执行
子类成员变量初始化
子类实例代码块执行
子类构造方法执行

结果分析:

  • 静态阶段仅执行 1 次(父→子);
  • 每次new对象时,实例阶段按「父成员→父实例块→父构造→子成员→子实例块→子构造」执行;
  • 同一阶段内,变量和代码块按书写顺序执行。

简化记忆口诀

  1. 静态先,实例后;
  2. 父类先,子类后;
  3. 变量 / 代码块按书写顺序,构造方法最后;
  4. 静态只一次,实例每次new

常见面试题陷阱

  • 问:子类静态代码块和父类构造方法谁先执行?

    答:子类静态代码块(静态阶段整体优先于实例阶段)。

  • 问:实例代码块和构造方法的关系?

    答:实例代码块是构造方法的 “前置逻辑”,编译后会被插入到构造方法中(super()

    之后,构造方法代码之前)。

  • 问:静态变量赋值引用了其他类的静态变量,会触发什么?

    答:会先加载并初始化被引用的类(遵循父类静态优先规则)。