成员变量显式赋值与实例代码块按源码声明顺序交错执行,构造方法体最后执行;父类初始化完成后才执行子类字段/块,再执行子类构造体。

Java 成员变量的初始化块(即非静态代码块)和构造方法的执行顺序,不是靠记忆口诀,而是由 JVM 实例初始化流程严格决定的:成员变量显式赋值、实例代码块、构造方法体,三者按源码声明顺序交替执行,且全部发生在父类构造方法返回之后、子类构造方法体开始之前。
成员变量初始化与实例代码块谁先执行?
二者没有绝对先后,而是“按源码位置交错执行”。JVM 把字段显式初始化(如 String name = "Alice";)和实例代码块({ System.out.println("init"); })统一编译进构造器开头,按它们在类中从上到下的出现顺序依次执行。
- 如果字段定义在代码块之前,就先执行字段初始化,再执行代码块
- 如果代码块写在字段定义前面,就先运行代码块,再初始化字段
- 多个字段和多个实例块混合时,完全遵循“谁在上面,谁先跑”原则
构造方法体永远最后执行
所有字段赋值和实例代码块都属于“实例初始化阶段”,必须在构造方法体运行前完成。构造方法第一行默认是 super(),它会先触发父类整个实例初始化流程(父类字段 → 父类实例块 → 父类构造体),等父类构造体结束,才轮到当前类的字段/块,最后才是本类构造方法体内的逻辑。
- 这意味着你在构造方法里看到的 this.name,一定已是初始化块或字段赋值后确定的值
- 但若在父类构造器中调用被子类重写的方法,此时子类字段可能还没走到初始化阶段(仍为 null 或 0),容易引发空指针
实战验证:加日志看清真实顺序
写一个简单父子类,在关键位置插入带标识的打印语句,就能直观还原 JVM 的执行流:
- 父类静态块 → “[S] Fu static”
- 子类静态块 → “[S] Zi static”
- 父类字段初始化 → “[F] Fu field: x=1”
- 父类实例块 → “[I] Fu init”
- 父类构造体首行 → “[C] Fu ctor start”
- 子类字段初始化 → “[F] Zi field: list=new ArrayList()”
- 子类实例块 → “[I] Zi init”
- 子类构造体 → “[C] Zi ctor start”
输出顺序就是对象诞生的真实时间轴,不会跳步,也不受编译器优化影响。
常见陷阱与避坑建议
容易出错的地方往往不在顺序本身,而在对“可访问性”的误判:
- 实例代码块里能用 this,也能调用实例方法,但该方法若依赖尚未初始化的字段(比如写在它后面的 int y = 5;),运行时可能拿到默认值
- 不能在实例块里读取定义在它之后的字段(编译报 Illegal forward reference),但可以给它赋值
- 字段声明处的初始化表达式(如 Date now = new Date();)本质就是被编译器塞进了实例块的位置,不是“声明即执行”,而是在对象内存分配、字段设默认值之后才触发
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











