不一定。父类static{}块仅在子类初始化时直接或间接引用父类静态成员且父类未初始化时才执行;构造器中的super()调用才保证父类构造器先于子类执行。

子类构造时父类 <clinit></clinit> 一定先执行吗?
不一定。Java 类初始化(即执行 <clinit></clinit> 方法)是懒触发、单次、线程安全的,只在**首次主动使用该类时**发生。子类初始化不自动触发父类初始化——除非父类的某个静态成员被直接引用,或父类尚未初始化而子类的 <clinit></clinit> 中隐式依赖它(比如调用父类静态方法、访问父类静态字段等)。
常见错误现象:ExceptionInInitializerError 报在父类静态块里,但堆栈显示是从子类 new 或静态字段访问开始的——这说明父类初始化被“捎带”触发了,而非构造器本身驱动。
- 只有当父类
<clinit></clinit>尚未执行,且子类初始化过程**直接或间接引用了父类的静态成员**,JVM 才会先去执行父类<clinit></clinit> - 如果父类已初始化过,子类初始化时完全跳过父类
<clinit></clinit> - 父类构造器(
<init></init>)才是在子类构造器中显式/隐式super()调用时执行的,和<clinit></clinit>是两套机制
static {} 块在继承链中怎么触发?
每个类的 static {} 块只属于它自己的 <clinit></clinit>,不会被继承,也不会因子类初始化而“连带执行”,除非 JVM 主动加载并初始化该类。
使用场景:你写了一个 Base 类含 static { System.out.println("Base init"); },又写 Child extends Base,然后运行 new Child() —— 输出不一定有 "Base init",取决于 Base 是否已被初始化过。
立即学习“Java免费学习笔记(深入)”;
- 若此前从未访问过
Base的任何静态字段或方法,new Child()会触发Base.<clinit></clinit>(因为子类符号引用了父类,且父类未初始化) - 若之前执行过
Base.SOME_STATIC_FIELD,那new Child()不再触发Base.<clinit></clinit> - 若子类自己没定义
static{},它的<clinit></clinit>可能为空,但仍算作一次“类初始化事件”
为什么 Class.forName("Child") 和 new Child() 初始化行为不同?
关键在参数:Class.forName(String) 默认调用的是 Class.forName(String, boolean, ClassLoader) 的重载版本,其中第二个参数为 true,表示“初始化该类”。而 new 触发的是“类加载 + 链式初始化判断”,逻辑更精细。
性能影响:Class.forName("Child") 会强制初始化 Child 及其尚未初始化的、被直接引用的父类(如 Base 的静态字段出现在 Child.<clinit></clinit> 中),但不会无脑初始化整个继承树顶层的所有祖先。
-
Class.forName("Child")→ 触发Child.<clinit></clinit>(以及必要时的父类<clinit></clinit>) -
Class.forName("Child", false, loader)→ 只加载,不初始化,此时Child.<clinit></clinit>完全不执行 -
new Child()→ 先确保Child类已加载,再按需决定是否初始化Child和父类,取决于它们是否已被初始化过
容易忽略的初始化顺序陷阱
最常踩的坑是把“类初始化”和“实例初始化”混为一谈,尤其在静态字段依赖子类实现、或静态块里调用可被重写的方法时。
示例:Base 有 static final String NAME = getValue();,而 getValue() 是非 private / 非 static 方法,子类重写了它——这时 Base.<clinit></clinit> 执行期间会调用子类的 getValue(),但此时子类实例还没创建,字段可能为默认值(null、0 等),导致 NPE 或逻辑错乱。
- 静态上下文里永远不要调用可被子类重写(即非 private / 非 static / 非 final)的方法
- 避免在父类静态字段初始化表达式中强依赖子类状态
- 注意
enum类的初始化:它的<clinit></clinit>会按声明顺序初始化所有枚举实例,且父类枚举的初始化早于子类枚举
复杂点在于:这个顺序不是写死的流程图,而是由字节码指令(getstatic、ldc、new)和类加载器的解析行为共同决定的,调试时得看 javap -c 输出的 <clinit></clinit> 指令流。







