类初始化触发时机包括:new实例、读写非final静态字段、调用静态方法、反射加载、启动主类;父类初始化优先于子类,且静态内容严格按继承链和源码书写顺序执行。

Java里类初始化,指的是JVM首次主动使用一个类时,执行其静态成员(静态变量赋值、静态代码块)的过程;它只发生一次,且严格按“父类优先、书写顺序”执行。
类初始化触发时机有哪些
不是一加载就初始化,而是等到真正“用到”才触发。常见场景包括:
- 执行
new创建该类实例 - 读取或设置非
final静态字段(如MyClass.count),但访问public static final int VERSION = 1;不会触发 - 调用该类的静态方法(如
MyClass.doWork()) - 通过反射(如
Class.forName("MyClass"))主动加载 -
虚拟机启动时指定的主类(含
main方法的类)
注意:子类初始化前,JVM会自动检查并先完成其父类的初始化——哪怕你只写 new Child(),Parent 的静态部分也早已跑完。
静态初始化顺序怎么排
静态内容执行顺序由两层规则决定:继承链顺序 + 类内书写顺序。具体是:
立即学习“Java免费学习笔记(深入)”;
- 先父类所有静态变量声明与赋值(按代码顺序)→ 父类所有静态块(按出现顺序)
- 再子类所有静态变量声明与赋值(按代码顺序)→ 子类所有静态块(按出现顺序)
- 每个静态变量的初始化表达式(比如调用方法)会在其声明位置立即执行,不是等所有变量声明完再统一赋值
看这个例子:
class Parent {
static int a = init("a");
static { System.out.println("Parent static block"); }
static int b = init("b");
static int init(String s) { System.out.println("init " + s); return 1; }
}
class Child extends Parent {
static int c = init("c");
static { System.out.println("Child static block"); }
}
public class Test {
public static void main(String[] args) {
new Child();
}
}
输出是:init aParent static blockinit binit cChild static block
——完全按源码中出现的位置线性执行,没有“先全声明再全赋值”这种中间状态。
为什么构造器里调用重写方法很危险
这不是静态初始化的问题,但常因混淆初始化阶段而踩坑:在父类构造器中调用一个被子类重写的方法(比如 init()),此时子类的实例变量和实例块尚未执行,但子类方法却已运行,导致访问到未初始化的字段(值为 null 或 0)。
例如:
class Parent {
Parent() { init(); } // 构造器中调用
void init() { System.out.println("Parent init"); }
}
class Child extends Parent {
String name = "child"; // 实例变量
void init() { System.out.println("Child init: " + name); } // 重写
}
执行 new Child() 时,name 还是 null,输出为:Child init: null。问题根源在于:实例初始化(变量赋值、实例块)发生在父类构造器执行之后、子类构造器执行之前,而父类构造器中已提前调用了子类方法。
真正容易被忽略的是:静态初始化虽只一次,但若其中抛出异常(比如 static { throw new RuntimeException(); }),整个类会进入“初始化失败”状态,后续任何对该类的主动使用都会直接抛出 ExceptionInInitializerError,且无法重试——连 Class.forName 都会失败。这类错误往往藏得深,日志里只显示顶层异常,需往堆栈深处找根本原因。










