Java中的数据初始化
# Java中的数据初始化
# 初始化的背景
Java 程序在运行时,会涉及 类的加载过程(Class Loading)。
当类被使用时,JVM 会按顺序执行:
加载(Loading)
→ 验证(Verification)
→ 准备(Preparation)
→ 解析(Resolution)
→ 初始化(Initialization)
2
3
4
5
其中 初始化(Initialization) 是最后一步,它的作用是:
初始化阶段:执行类中的 初始化逻辑
- 给 静态变量 赋值(包括显示赋值和静态代码块)
- 执行类的
static {}静态代码块 - 确保类处于可用状态
它让类从“已加载”状态变成“已准备好正常使用”的状态。
# 一些追问
类初始化和构造方法是什么关系?
它们的关系是:类初始化是“类级别”的动作,构造方法是“对象级别”的动作。
类初始化 → 针对类||构造方法调用 → 针对对象实例
为什么要分“类初始化”和“构造方法”两个机制?
- 因为 Java 是面向对象的,类和对象是两个层级
- 类本身需要被初始化(静态资源准备)
- 对象实例也需要被初始化(实例字段准备)
如果不分层级,会造成混乱、效率低下。
# Java中数据的初始化-顺序
Java 的数据初始化流程,我们分为 两大部分:类初始化(静态) 和 对象初始化(实例)。
这里我们先不讨论,JVM那一层对文件的读取,加载这些步骤,只关注,Java中的类和对象以及对应数据(或者属性),他们的初始化顺序以及注意点是什么?
本次,我们只关注————初始化(Initialization),这个步骤。
# 静态变量和对象初始化
# 静态数据初始化
执行静态变量的显示初始化 + 静态代码块(按代码顺序),静态变量和静态代码块的初始化顺序严格按照代码中出现的顺序
- 如果调用,那么顺序就是这样的,静态变量显式赋值 → 静态代码块 → 静态方法,但一定注意,先初始化的在前。
public class A {
static int x = initX(); // 1
static { // 2
System.out.println("static block");
}
static int y = 100; // 3
}
2
3
4
5
6
7
注意:
初始化顺序问题:如果一个静态变量依赖于另一个静态变量,必须确保被依赖的变量先声明
不能在局部变量中使用:
static不能用于方法内部的局部变量不能访问非静态成员:静态变量不能直接访问实例变量或实例方法
生命周期:与程序生命周期相同,从类加载开始到程序结束
# 对象初始化
接着是,对象初始化(Instance Initialization)
对应代码中就是,使用 new 创建对象时,如下列示例:
A a = new A();
每次创建对象时,实例的初始化,大概要经历以下几个过程:
- 父类的实例变量(默认初始化 → 显式初始化)
- 父类的实例代码块(即非静态代码块 )
- 父类的构造器
- 子类的实例变量(默认初始化 → 显式初始化)
- 子类的实例代码块
- 子类的构造器
下面是更详细的说明:
1.为对象分配内存,所有字段设为默认值
// 比如这样
int age = 18;
String name = "Tom";
2
3
2.调用父类构造器(super())
构造器第一行 隐式存在 super(),由 JVM 强制执行。
并且在调用构造方法前会执行父类的:
- 实例字段初始化
- 实例代码块
父类实例变量初始化
父类实例代码块
父类构造方法
2
3
3.初始化本类实例变量和实例代码块(按代码顺序)
比如像下面这样:
public class A {
int x = initX(); // 1
{ // 2
System.out.println("instance block");
}
int y = 10; // 3
A() { // 4
System.out.println("constructor");
}
}
2
3
4
5
6
7
8
9
10
11
# 总结
# 为什么顺序是这样?(核心原因)
Java 的初始化设计遵循 从父到子、从静态到实例、从默认值到显示赋值 的原则。
原因如下:
- 静态属于类,本质上在对象存在前就必须完成
静态成员在 JVM 的 Method Area 中,但实例成员在堆里。 只有类先初始化,才能创建对象。
- 父类决定子类的内存结构,因此必须先初始化父类
因为子类对象 包含父类部分(继承结构):
Object -> Parent -> Child
- 构造方法要使用实例字段,因此字段必须先初始化
例如构造方法可能访问字段:
this.x += 1;
如果字段还未初始化,就无法正确执行构造逻辑。
# 加载顺序
类加载阶段(一次):
-------------------------------------------
1. 静态变量默认值
2. 静态变量显示初始化
3. 静态代码块
-------------------------------------------
对象实例化阶段(每次 new):
-------------------------------------------
4. 父类实例变量默认值
5. 父类实例变量显示初始化 + 父类实例代码块
6. 父类构造方法
-------------------------------------------
7. 子类实例变量默认值
8. 子类实例变量显示初始化 + 子类实例代码块
9. 子类构造方法
-------------------------------------------
对象创建完成
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 一些注意点
- 变量和方法
“在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。”
- 静态数据
“无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。”
- 代码块
代码块就是显式的初始化。非静态代码块,在创建对象时执行,且在构造方法之前执行。无论 new 多少个对象,静态代码块只运行一次,在类初始化时发生。