java 类加载机制中的被动引用是指不会触发类初始化(即不执行 <clinit></clinit> 方法)的引用场景。主要包含三种情况:通过子类引用父类的静态字段、通过数组定义引用类、以及常量在编译期直接存入调用类的常量池中。

通过子类引用父类的静态字段
当程序通过子类去访问父类中定义的静态变量时,只会触发父类的初始化,而不会触发子类的初始化。这是因为静态变量属于类本身,子类只是继承了该变量,并未在子类中重新定义它,因此不需要对子类进行初始化。

public class Parent {
public static int value = 100;
static {
System.out.println("Parent clinit");
}
}
public class Child extends Parent {
static {
System.out.println("Child clinit");
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
System.out.println(Child.value);
}
}
运行上述代码,控制台仅输出 Parent clinit 和 100,证明子类 Child 的 <clinit></clinit> 方法未被执行。
立即学习“Java免费学习笔记(深入)”;
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
通过数组定义引用类
如果创建了一个数组对象,其元素类型是某个类,那么 JVM 只会为这个数组类型生成一个新的类(如 [LClassName;),而不会触发原类 ClassName 的初始化。数组类本身会初始化,但被引用的类不会。

public class ArrayTest {
static {
System.out.println("ArrayTest clinit");
}
public static void main(String[] args) {
ArrayTest[] array = new ArrayTest[10];
// 这里只触发了数组类的初始化,未触发 ArrayTest 的初始化
}
}
运行上述代码,控制台没有任何输出,说明 ArrayTest 类没有被初始化。只有当真正使用数组中的元素(如 array[0] = new ArrayTest();)时,才会触发 ArrayTest 的初始化。
常量在编译期直接存入调用类的常量池
当引用一个类时,如果该引用涉及的是 static final 常量,且常量值在编译期已经确定(即字面量或编译期可计算的表达式),那么编译器会将该常量的值直接存入调用该常量的类的常量池中。此时,调用类与常量所在的类之间没有依赖关系,因此不会触发常量所在类的初始化。
例如,若 Constant.java 中定义了 public static final String MSG = "Hello";,而在 Main.java 中使用 System.out.println(Constant.MSG);,JVM 实际上打印的是字符串 Hello,而不是从 Constant 类中获取。因此 Constant 类不会被加载和初始化。但如果常量值不是编译期常量(如 public static final String MSG = String.valueOf(Math.random());),则会触发类的初始化。

















