0%

初始化

Java 初始化顺序详解:从类加载到对象创建

Java 中的初始化是一个涉及类加载对象实例化的复杂过程,其顺序严格遵循特定规则。理解初始化顺序对于排查程序启动问题、确保资源正确初始化至关重要。本文将从对象实例化和类加载两个维度,详细解析 Java 的初始化机制。

对象实例化时的初始化顺序

当通过 new 关键字创建对象时,初始化顺序遵循 “先父后子、先成员后构造” 的原则,具体步骤如下:

  1. 父类的非静态成员变量(按定义顺序初始化)
  2. 父类的非静态初始化块(按出现顺序执行)
  3. 父类的构造器主体(构造器内的代码)
  4. 本类的非静态成员变量(按定义顺序初始化)
  5. 本类的非静态初始化块(按出现顺序执行)
  6. 本类的构造器主体

示例验证:对象实例化顺序

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
54
55
56
57
58
// 父类
class Parent {
// 父类非静态成员变量
private ParentMember p1 = new ParentMember(1);

// 父类非静态初始化块
{
System.out.println("Parent 非静态初始化块");
}

// 父类非静态成员变量(定义顺序靠后)
private ParentMember p2 = new ParentMember(2);

// 父类构造器
public Parent() {
System.out.println("Parent 构造器主体");
}
}

// 父类的成员类
class ParentMember {
public ParentMember(int id) {
System.out.println("ParentMember(" + id + ") 初始化");
}
}

// 子类
class Child extends Parent {
// 子类非静态成员变量
private ChildMember c1 = new ChildMember(1);

// 子类非静态初始化块
{
System.out.println("Child 非静态初始化块");
}

// 子类非静态成员变量(定义顺序靠后)
private ChildMember c2 = new ChildMember(2);

// 子类构造器
public Child() {
System.out.println("Child 构造器主体");
}
}

// 子类的成员类
class ChildMember {
public ChildMember(int id) {
System.out.println("ChildMember(" + id + ") 初始化");
}
}

// 测试
public class InitOrderTest {
public static void main(String[] args) {
new Child(); // 创建子类对象
}
}
执行结果:
1
2
3
4
5
6
7
8
ParentMember(1) 初始化    // 父类非静态成员(顺序1)
Parent 非静态初始化块 // 父类非静态初始化块(顺序2)
ParentMember(2) 初始化 // 父类非静态成员(顺序1,按定义顺序)
Parent 构造器主体 // 父类构造器(顺序3)
ChildMember(1) 初始化 // 子类非静态成员(顺序4)
Child 非静态初始化块 // 子类非静态初始化块(顺序5)
ChildMember(2) 初始化 // 子类非静态成员(顺序4,按定义顺序)
Child 构造器主体 // 子类构造器(顺序6)
结论:
  • 非静态成员变量和非静态初始化块的执行顺序由定义 / 出现顺序决定(先定义的先执行)。
  • 非静态初始化逻辑(成员变量 + 初始化块)总是在构造器主体之前执行(构造器的作用是 “收尾” 初始化)。
  • 继承场景下,父类完全初始化后才会初始化子类(确保子类使用父类成员时已就绪)。

类加载时的静态初始化顺序

类加载(Class Loading)是 JVM 将类的字节码加载到内存并生成 Class 对象的过程,此阶段会执行静态初始化逻辑(静态成员变量 + 静态初始化块)。静态初始化仅在类首次被使用时执行一次,顺序如下:

  1. 父类的静态成员变量(按定义顺序初始化)
  2. 父类的静态初始化块(按出现顺序执行)
  3. 本类的静态成员变量(按定义顺序初始化)
  4. 本类的静态初始化块(按出现顺序执行)

触发类加载的场景

类不会主动加载,需通过特定操作触发,常见场景包括:

  • 首次创建类的实例(new Child());
  • 访问类的静态非 final 变量静态非编译期常量
  • 调用类的静态方法;
  • 通过 Class.forName("全类名") 主动加载;
  • 初始化子类时,父类会被优先加载(即使未直接使用父类)。

示例验证:静态初始化与类加载触发条件

用户提供的 Initable 系列类示例很好地展示了类加载的触发规则,我们结合结果分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 测试类执行结果:
Initable---------------
Initable初始化 // 触发Initable类加载
457 // 非编译期常量的值
Initable1---------------
47 // 编译期常量,未触发类加载
Initable2---------------
Initable2初始化 // 触发Initable2类加载
56 // 静态非final变量的值
Initable3---------------
(无输出) // .class语法未触发初始化
Initable4---------------
Initable4初始化 // Class.forName触发类加载
关键结论:
  1. 静态 final 变量的特殊性
    • 若为编译期常量(如 Initable1.COUNT = 47,值在编译时确定),访问时不触发类加载(JVM 直接从常量池获取值)。
    • 若为非编译期常量(如 Initable.COUNT = new Random().nextInt(1000),值在运行时确定),访问时触发类加载(需执行计算逻辑)。
  2. 静态非 final 变量
    访问时(如 Initable2.COUNT必然触发类加载(需为变量分配内存并初始化)。
  3. 获取 Class 对象的两种方式
    • Initable3.class:仅获取类的 Class 对象不触发初始化(类加载的 “加载” 阶段完成,但 “初始化” 阶段未执行)。
    • Class.forName("Initable4"):主动触发类的完整加载流程(包括初始化)。

综合初始化顺序(类加载 + 对象实例化)

当首次创建对象时,完整流程是 “类加载(静态初始化)→ 对象实例化(非静态初始化)”,具体步骤:

  1. 加载父类 → 执行父类静态初始化(静态成员 + 静态块)
  2. 加载本类 → 执行本类静态初始化(静态成员 + 静态块)
  3. 执行父类非静态初始化(非静态成员 + 非静态块)→ 父类构造器
  4. 执行本类非静态初始化(非静态成员 + 非静态块)→ 本类构造器

示例:完整流程验证

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class Grandparent {
// 静态初始化
static {
System.out.println("Grandparent 静态块");
}
static int gStatic = 1;

// 非静态初始化
{
System.out.println("Grandparent 非静态块");
}
GrandparentMember gm = new GrandparentMember();

public Grandparent() {
System.out.println("Grandparent 构造器");
}
}

class GrandparentMember {
public GrandparentMember() {
System.out.println("GrandparentMember 初始化");
}
}

// 父类
class Parent extends Grandparent {
static {
System.out.println("Parent 静态块");
}
static int pStatic = 2;

{
System.out.println("Parent 非静态块");
}
ParentMember pm = new ParentMember();

public Parent() {
System.out.println("Parent 构造器");
}
}

class ParentMember {
public ParentMember() {
System.out.println("ParentMember 初始化");
}
}

// 子类
class Child extends Parent {
static {
System.out.println("Child 静态块");
}
static int cStatic = 3;

{
System.out.println("Child 非静态块");
}
ChildMember cm = new ChildMember();

public Child() {
System.out.println("Child 构造器");
}
}

class ChildMember {
public ChildMember() {
System.out.println("ChildMember 初始化");
}
}

// 测试(首次创建Child对象)
public class FullInitOrder {
public static void main(String[] args) {
new Child();
}
}
执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 类加载(静态初始化)
Grandparent 静态块 // 父类的父类静态块
Parent 静态块 // 父类静态块
Child 静态块 // 本类静态块

// 对象实例化(非静态初始化)
Grandparent 非静态块 // 父类的父类非静态块
GrandparentMember 初始化 // 父类的父类成员
Grandparent 构造器 // 父类的父类构造器
Parent 非静态块 // 父类非静态块
ParentMember 初始化 // 父类成员
Parent 构造器 // 父类构造器
Child 非静态块 // 本类非静态块
ChildMember 初始化 // 本类成员
Child 构造器 // 本类构造器

特殊注意事项

  1. 构造器中的 super()
    子类构造器的第一行默认隐含 super()(调用父类无参构造器),若父类无无参构造器,需显式调用 super(参数),否则编译报错。这确保了 “先父后子” 的初始化顺序。
  2. 静态初始化仅执行一次
    无论创建多少个对象,类的静态初始化(静态成员 + 静态块)仅在首次加载类时执行一次
  3. 默认构造器
    若类未显式定义构造器,编译器会生成无参构造器;若显式定义了构造器,默认构造器将不再生成(建议手动保留无参构造器,避免子类初始化问题)。
  4. 初始化块与成员变量的顺序
    非静态初始化块和非静态成员变量的执行顺序由代码中的出现顺序决定(先写的先执行),与位置(构造器前 / 后)无关(如用户提供的 House 类示例)

欢迎关注我的其它发布渠道