静态代码块在类初始化阶段执行且仅一次,构造代码块每次实例化时在构造函数前执行;父子类顺序为父static→子static→父init→父ctor→子init→子ctor。

静态代码块在类加载时只执行一次
静态代码块(static {})由 JVM 在类初始化阶段(即首次主动使用该类时)执行,且仅执行一次。它不依赖对象创建,哪怕你 never new 一个实例,只要触发了类初始化(比如访问静态字段、调用静态方法、反射 Class.forName()),它就会跑。
常见误判是以为「写了 static 块就马上执行」——其实得等到类真正被初始化,而不是被加载或链接。JVM 加载(Loading)类时不会执行 static 块,只有到初始化(Initialization)阶段才执行。
- 触发类初始化的典型操作:
new对象、访问public static字段(非编译期常量)、调用public static方法、反射Class.forName("X") - 不触发初始化的操作:
ClassLoader.loadClass("X")、访问final static编译期常量(如public static final int A = 1;) - 子类引用父类的静态字段,若该字段不是子类定义的,则只初始化父类(不触发子类 static 块)
构造代码块在每次实例化时执行,且早于构造函数
构造代码块({},无 static 修饰)属于实例初始化的一部分,每次调用 new 都会执行一次,且严格在对应构造函数体执行之前运行。
它的执行时机和顺序容易被忽略:不是“写在哪儿就从哪儿开始执行”,而是由 JVM 统一收集到每个构造函数开头(在 super() 调用之后、本构造函数代码之前)。
- 多个构造代码块按源码出现顺序合并执行
- 即使构造函数里写了
return,构造块也已执行完毕——它不依赖构造函数是否走完 - 如果构造函数第一行显式调用了
this(...),那当前构造块不会执行,而是跳去目标构造函数对应的构造块
静态块、构造块、构造函数的完整执行顺序
当第一次 new Child() 时,JVM 按以下顺序执行(以父子类为例):
父类 static 块 → 子类 static 块 → 父类构造块 → 父类构造函数 → 子类构造块 → 子类构造函数
这个顺序固定,由 JVM 规范保证。注意:static 块只在首次触发类初始化时跑;后续再 new 多少次,都只跑构造块 + 构造函数。
- 父类 static 块一定在子类 static 块之前(哪怕子类先被引用)
- 构造块总在对应构造函数体之前,哪怕构造函数里有 super() 或 this(),JVM 已提前把块内容“注入”进字节码
- 如果子类没写任何构造函数,编译器生成默认无参构造函数,仍会执行所有构造块
调试时如何验证执行顺序?别靠猜,加日志或断点
光看代码结构很难确认实际执行流,尤其涉及继承、多构造函数、this() 重载时。最可靠的方式是打带类名和阶段标识的日志,或者在 IDE 中对每个块设断点观察调用栈。
示例片段(可直接运行验证):
class Parent {
static { System.out.println("Parent static"); }
{ System.out.println("Parent init"); }
Parent() { System.out.println("Parent ctor"); }
}
class Child extends Parent {
static { System.out.println("Child static"); }
{ System.out.println("Child init"); }
Child() { System.out.println("Child ctor"); }
}
// 执行 new Child() 输出:
// Parent static
// Child static
// Parent init
// Parent ctor
// Child init
// Child ctor
关键细节:如果删掉 Child 的构造函数,输出不变;如果只调用 Parent.class,则只输出 Parent static;如果访问 Child.SOME_STATIC_FIELD,会触发 Parent static 和 Child static(因为子类初始化前必须确保父类已初始化)。
最容易漏掉的是「static 块不随对象生命周期变化」——它属于 Class 对象,而 Class 对象在同一个类加载器下全局唯一。这点在热部署、OSGi、模块化场景中特别容易引发意外行为。










