类初始化触发静态变量与静态代码块执行,按从上到下、先父后子顺序执行一次;调用子类静态方法会触发父类和子类初始化,final static编译期常量除外。

静态方法被调用时,变量加载的触发逻辑并不取决于“方法是否被调用”,而取决于“类是否被初始化”。Java(及多数JVM语言)中,类初始化发生在首次主动使用该类时,而非首次声明或引用。代码块的执行顺序,本质是类加载过程中“准备 → 解析 → 初始化”三阶段里“初始化阶段”的具体表现。
静态变量与静态代码块的加载时机
静态变量(含静态常量、静态对象)和静态代码块(static {})都在类的初始化阶段按**从上到下、先父后子**的顺序执行一次,且仅执行一次。关键点:
- 即使静态方法里只读取一个静态字段,也会触发整个类的初始化(包括所有静态变量赋值和静态块)
- final static 基本类型常量(如 public static final int MAX = 100;)属于“编译期常量”,不触发类初始化;但若通过方法赋值(final static int MAX = compute();),则会触发
- 子类调用父类静态方法,不会触发子类初始化,但会触发父类初始化(如果父类尚未初始化)
实战验证:通过日志观察加载顺序
写一段可运行的代码,明确标识各环节:
class Parent {
static { System.out.println("Parent static block"); }
static String parentField = initParent();
static String initParent() {
System.out.println("Parent field init");
return "p";
}
}
class Child extends Parent {
static { System.out.println("Child static block"); }
static String childField = initChild();
static String initChild() {
System.out.println("Child field init");
return "c";
}
static void say() {
System.out.println("Child.say called");
}
}
执行 Child.say() 输出为:
Parent static blockParent field init
Child static block
Child field init
Child.say called
说明:调用子类静态方法 → 触发子类初始化 → 子类初始化前先完成父类初始化 → 所有静态成员按声明顺序逐个初始化。
常见误区与避坑提示
- “只用静态方法,不碰静态变量” ≠ 不触发初始化:只要该方法在初始化阶段之后才可调用,而初始化的前提就是“首次主动使用”,调用静态方法本身就是主动使用
- 接口没有静态代码块,但有 static final 字段:接口的字段访问一般不触发接口初始化(除非字段不是编译期常量且被显式访问)
- ClassLoader.defineClass 不等于初始化:类被加载(Loading)不等于被初始化(Initialization);只有首次主动使用才进入初始化
- 多线程下类初始化是安全的:JVM 保证类初始化过程加锁,同一类不会被重复初始化
延伸:如何延迟加载静态资源?
若希望静态字段(如大缓存、数据库连接)不在类加载时立即创建,可用“静态内部类 Holder 模式”:
class ResourceLoader {
private static class Holder {
static final ExpensiveResource INSTANCE = new ExpensiveResource();
}
static ExpensiveResource getInstance() {
return Holder.INSTANCE; // 首次调用 getInstance 才触发 Holder 初始化
}
}
这种方式利用了“内部类初始化时机由外部类主动触发决定”的特性,实现按需加载,避免启动时阻塞。










