静态成员在类加载时按源码顺序执行,父类先于子类;实例成员每次new时按继承链从父到子执行,非静态块与字段初始化等价且位于构造器前。

静态成员:类加载时一次性按序执行
静态变量赋值和静态代码块被JVM合并进同一个<clinit></clinit>方法,从上到下逐行执行。谁在源码里写得靠前,谁就先运行——比如static int a = initA();出现在static { }之前,那initA()一定先调用。
注意两点:静态块中不能引用其后声明的静态变量(编译报错“非法前向引用”);即使只声明没赋值(如static String s;),也会在准备阶段设为null,但显式初始化动作仍严格按位置执行。
任一静态初始化语句抛出未捕获异常(如NullPointerException),整个<clinit></clinit>中断,后续对该类所有访问都会触发NoClassDefFoundError。
继承场景:父子类静态部分强制分层执行
子类首次主动使用时,JVM保证父类静态成员全部初始化完成后再执行子类静态部分。顺序固定为:
- 父类静态变量 → 父类静态块
- 子类静态变量 → 子类静态块
这个顺序由双亲委派机制保障,无法跳过或倒置。子类静态块里可安全使用父类public static字段;反过来则不行——父类静态块不能依赖子类静态状态。
final static基本类型常量(如public static final int MAX = 100;)属于编译期常量,不触发类初始化。
实例成员:每次new都重走一遍父→子链路
对象创建时,实例变量、非静态代码块和构造方法按继承层级逐层展开:
- 先父类:按源码顺序执行实例变量赋值 + 非静态代码块,再进入父类构造方法体
- 再子类:同样顺序执行子类实例变量赋值 + 非静态代码块,最后执行子类构造方法体
非静态代码块和字段初始化语句地位等同,编译后被插入每个构造方法开头(super()之后),所以总在构造逻辑之前运行。
实例部分每次new都执行,而静态部分全局只执行一次。
调试验证:用日志锚定真实执行流
最直接的方式是加带标识的日志输出,配合首次触发点观察:
- 在
main第一行前访问某个静态字段(如System.out.println(Child.flag);),就能完整触发类初始化流程 - 在四类位置加明确日志:
[S]静态块、[I]实例块、[C]构造器首行、[F]字段初始化方法内
常见问题包括:静态块里引用子类尚未初始化的字段 → 把依赖移到 Holder 模式或调整声明顺序;构造器中调用可被子类重写的方法 → 此时子类实例字段尚未初始化,容易读到0或null。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











