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. 自检

  1. 六步初始化顺序是什么?为什么"父构造器调子类方法"会拿到默认值?
  2. 静态块什么时候执行?抛异常会怎样?
  3. 静态内部类与成员内部类的差别?
  4. final int[] a 能否改 a[0]?
  5. 数组协变为什么会导致 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/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。