1314 字
约 4 分钟
6
S04-B04 · 数组、static 与初始化顺序
适用读者:被"静态块什么时候执行"和"对象到底怎么初始化"绕晕的人。
前置:S04-B02。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1720(124) A-1661(118) A-1648(50) A-1647(77) A-1654(50) A-1653(79) = 498 点。
覆盖扩展:E-S04-005(初始化顺序)。
上一篇:S04-B03 · 流程控制与运算符
下一篇:S04-B05 · 封装、继承与多态。
1. 一句话结论
static 属于类(类加载时执行一次),实例成员属于对象(每次 new 执行);继承下两者的执行顺序是固定的六步。
2. 实测:new Child() 的六步顺序
class Parent {
static { System.out.println("1. 父静态块"); }
{ System.out.println("3. 父实例块"); }
Parent() { System.out.println("4. 父构造器"); }
}
class Child extends Parent {
static { System.out.println("2. 子静态块"); }
{ System.out.println("5. 子实例块"); }
Child() { System.out.println("6. 子构造器"); }
}
实测输出:
--- new Child() ---
1. 父静态块
2. 子静态块
3. 父实例块
4. 父构造器
5. 子实例块
6. 子构造器
| 步 | 时机 | 次数 |
|---|---|---|
| 1–2 静态块 | 类加载时(第一次使用该类) | 各 1 次 |
| 3 父实例块 | 每次 new 子类对象时 |
每次 1 次 |
| 4 父构造器 | 同上 | 每次 |
| 5 子实例块 | 同上 | 每次 |
| 6 子构造器 | 同上 | 每次 |
这条顺序解释了三个常见 bug:
| bug | 原因 |
|---|---|
| 父构造器里调用被子类重写的方法,拿到子类字段的默认值 | 父构造器执行时子类字段还没初始化(第 4 步早于第 5 步) |
| 静态块里访问实例变量 → 编译错误 | 静态上下文没有 this |
| 子类静态块里读父类静态字段,拿到默认值 | 父静态块先执行,但字段赋值可能在其后(取决于声明顺序) |
3. static 的四种用法(A-1661)
| 用法 | 说明 | 注意 |
|---|---|---|
| 静态变量 | 类级共享 | 可变静态变量 = 全局状态(并发风险) |
| 静态方法 | 不需要实例 | 不能访问实例成员 |
| 静态块 | 类加载时执行一次 | 抛异常会导致 ExceptionInInitializerError |
| 静态内部类 | 不持有外部类引用 | 与成员内部类的关键差别 |
内部类的取舍(A-1654):
| 类型 | 持有外部实例 | 用途 |
|---|---|---|
| 成员内部类 | ✅ | 需要访问外部实例状态 |
| 静态内部类 | ❌ | 不需要外部实例时优先(避免内存泄漏) |
| 局部内部类 | ✅ | 方法内临时用 |
| 匿名内部类 | ✅ | 一次性实现(Lambda 可替代时优先 Lambda) |
4. 数组:它是个对象(A-1720)
| 特性 | 说明 |
|---|---|
| 类型 | int[] 是对象,有 length 字段(不是 length()) |
| 默认值 | new int[3] 全 0;引用类型全 null |
| 越界 | ArrayIndexOutOfBoundsException |
| 协变 | Object[] objs = new String[1] 合法 → 但存 Integer 会 ArrayStoreException |
| 拷贝 | Arrays.copyOf(浅拷贝) |
判据:需要动态扩容就用 ArrayList;数组只用于"长度固定且性能敏感"的场景。
5. 修饰符速查(A-1647、A-1653)
| 修饰符 | 作用 | 常见坑 |
|---|---|---|
private/protected/public |
可见性 | protected 还包含"同包" |
final |
变量不可变 / 方法不可重写 / 类不可继承 | final 引用 ≠ 对象不可变 |
static |
类级 | 见 §3 |
abstract |
不能实例化 | 有抽象方法的类必须 abstract |
final 的三层含义(A-1653):
| 位置 | 含义 |
|---|---|
final 变量 |
只能赋值一次(引用不可变,对象内容可变) |
final 方法 |
不能被子类重写 |
final 类 |
不能被继承(如 String) |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "静态块每次 new 都执行" | 只在类加载时一次 |
| "父构造器里能安全调用子类方法" | 子类字段还没初始化 |
"final int[] a 数组内容不可变" |
引用不可变,元素可变 |
| "内部类都持有外部引用" | 静态内部类不持有 |
"数组 length()" |
是字段 length |
7. 自检
- 六步初始化顺序是什么?为什么"父构造器调子类方法"会拿到默认值?
- 静态块什么时候执行?抛异常会怎样?
- 静态内部类与成员内部类的差别?
final int[] a能否改a[0]?- 数组协变为什么会导致
ArrayStoreException?
8. 下一篇
S04-B05 · 封装、继承与多态:到底在解决什么问题
初始化顺序搞清后,下一篇讲 OOP 三大特性——以及"多态靠什么实现"(方法表/动态分派)。
本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1720/1661/1648/1647/1654/1653;扩展点 E-S04-005。初始化顺序为 OpenJDK 21 实测。
S04-B04 · 数组、static 与初始化顺序
https://www.clxhxhhr.top/posts/5219/ 评论
0 条
还没有评论,先写一条吧。