java继承初始化顺序严格遵循“静态优先、父类先行”原则:静态成员按源码顺序在中执行,父类完成后才执行子类;实例成员在每个构造器super()后按源码顺序插入赋值与实例块。

Java 继承中的初始化顺序不能靠“猜”或“试”,必须从源码结构和 JVM 编译机制出发,逐层拆解。核心是抓住两个关键分界:类加载阶段(静态) vs 对象创建阶段(实例),以及继承链上“父类先行”的强制约束。
静态部分:看源码声明顺序 + 继承层级
静态变量赋值和 static{} 块在编译后被合并进同一个 <clinit></clinit> 方法,JVM 按它们在源码中**从上到下出现的顺序**执行:
- 父类中先声明的 static 变量 → 父类中后声明的 static 变量 → 父类中第一个 static{} → 父类中第二个 static{}
- 父类
<clinit></clinit>执行完,才轮到子类的 static 成员,同样按源码顺序执行 - 静态块里若引用了其后才声明的 static 字段(如
System.out.println(x); static int x = 1;),编译直接报错:“非法前向引用”
实例部分:看构造器插入点 + 编译重写逻辑
非静态变量赋值(如 int x = 5;)和实例代码块({ ... })在编译时被**自动插入每个构造器开头、super() 调用之后的位置**。这意味着:
- 父类构造器体执行前,已按源码顺序完成:父类实例变量赋值 → 父类实例块
- 子类构造器体执行前,同样完成:子类实例变量赋值 → 子类实例块
- 即使子类有多个构造器(含带参、无参),所有构造器都会被注入相同的初始化序列
继承链上的完整调用流:从字节码视角可验证
以 new Child() 为例,实际触发的初始化链条在字节码层面清晰可见:
- 触发
Child.class加载 → 先确保Parent.class已完成<clinit></clinit>→ 执行Parent.<clinit></clinit>→ 执行Child.<clinit></clinit> - 进入对象创建:
Child.<init></init>中第一指令是invokespecial Parent.<init></init> - 而
Parent.<init></init>开头已包含父类字段赋值与实例块;Child.<init></init>在调用完super()后,立即插入子类字段赋值与实例块
容易误读的源码“假象”
有些写法看似改变了顺序,实则只是掩盖了底层规则:
-
static final String CONST = "a" + "b";是编译期常量,不触发类初始化,不会出现在<clinit></clinit>中 - 在静态块中
new Parent(),会提前触发父类实例初始化,但此时父类<clinit></clinit>已执行完毕——不是“顺序乱了”,而是两个生命周期交叉 - 构造器里写
this.x = 10;不属于“初始化”,它发生在所有显式赋值和实例块之后,是运行期赋值
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











