类加载时先执行父类静态变量和静态代码块,再执行子类静态变量和静态代码块;对象创建时按父类实例变量默认值→父类非静态块与显式初始化→父类构造体→子类实例变量默认值→子类非静态块与显式初始化→子类构造体顺序执行。

排查继承关系中的初始化顺序,核心是分清两个阶段:类加载时的静态初始化、对象创建时的实例初始化。两者互不干扰,但执行时机和触发条件完全不同。只要拆开看,顺序就清晰了。
先确认静态部分是否已执行
静态变量和静态代码块只在类首次被主动使用时执行一次,比如第一次 new 子类()、调用子类的静态方法、或访问子类的 public static 字段。此时 JVM 会沿继承链自顶向下完成父类→子类的静态初始化:
- 父类静态变量(按声明顺序)→ 父类静态代码块(按书写顺序)
- 子类静态变量(按声明顺序)→ 子类静态代码块(按书写顺序)
如果某段静态代码没打印日志,先检查它是否属于尚未被触发的类——比如只 new 了父类,子类静态块就不会跑。
观察 new 过程中实例部分的执行流
每次 new 都会完整走一遍实例初始化链。关键点在于:所有实例变量赋值和非静态代码块,都被编译器自动“插入”到对应构造方法开头、super() 调用返回之后、构造方法体之前。所以实际顺序是:
- 父类实例变量设默认值(如 int=0,Object=null)
- 父类非静态代码块 + 实例变量显式初始化(如 String name = log("name init"))
- 父类构造方法体
- 子类实例变量设默认值
- 子类非静态代码块 + 实例变量显式初始化
- 子类构造方法体
建议在每个成员变量初始化表达式里加带标识的日志,例如 int x = log("x in Parent"),配合 System.out.println 打印,就能准确定位每一步在哪执行。
注意构造方法里的“陷阱”
构造方法不是初始化的起点,而是实例初始化流程的终点之一。它的第一行默认是 super(),而 super() 会触发父类整条实例初始化链。常见问题包括:
- 父类构造器中调用了被子类重写的方法,该方法又访问子类字段 → 此时子类字段还是默认值(null 或 0),容易 NPE
- 误以为 new Child() 先进子类构造器再初始化字段 → 实际上字段和代码块已在构造器体执行前完成
这类问题可通过在父类构造器开头加断点,逐步步入,观察字段真实值变化来验证。
用最小可运行示例验证
写一个只有静态块、实例块、构造器和简单字段赋值的父子类,运行后看输出顺序。例如:
Parent static → Child static → Parent init → Parent ctor → Child init → Child ctor
这个输出就是标准顺序的实证。任何偏离都说明代码中有隐式 super() 被覆盖、this() 调用、或静态依赖未满足等异常情况。

















