编译期常量是指满足public static final修饰、类型为基本类型或String、且初始化表达式在编译期可确定的字段,其值被直接内联到调用方字节码中,运行时不触发类加载或静态代码块执行;而非编译期常量则会触发类的完整初始化流程。

编译期常量(public static final修饰的基本类型或字符串字面量)不会触发类加载,因为它们的值在编译时就被直接“内联”到调用方的字节码中,运行时根本不会访问原类。
什么是编译期常量
满足以下全部条件的 static final 字段才是编译期常量:
- 声明为
public static final - 类型是基本类型(
int、boolean等)或String - 初始化表达式是编译期可确定的常量(如字面量
100、"hello",或由常量组成的简单运算,如2 + 3)
例如:public static final int MAX = 100; 或 public static final String NAME = "Tom"; 就是编译期常量;而 public static final String PATH = System.getProperty("user.home"); 不是——它依赖运行时环境,属于非编译期常量。
为什么不会触发类加载
编译器在生成 class 文件时,会把所有对这类常量的引用替换成实际值。比如调用 A.NAME,字节码里直接写的是 "Tom",而不是一条 getstatic 指令去读 A 类的字段。因此 JVM 运行时完全不知道 A 类存在,自然不会加载它。
立即学习“Java免费学习笔记(深入)”;
- 没有符号引用 → 不触发类加载流程的任何阶段(包括加载、准备、初始化)
- 即使 A 类中定义了静态代码块,也不会执行
- 哪怕 A 类根本不存在(被删了),只要编译通过,运行时仍能正常输出值(因为值早已固化在调用方字节码里)
非编译期常量的 static final 会触发初始化
只要 static final 字段的值不能在编译期确定,JVM 就必须在运行时读取它,这就构成“主动使用”,从而触发类的初始化(即执行 <clinit></clinit> 方法)。
- 例如:
public static final Date NOW = new Date();或public static final int CODE = computeCode(); - 此时类会经历完整加载流程,并执行所有静态代码块和静态变量赋值语句
- 注意:这触发的是“初始化”阶段,不是“加载”本身;但通常初始化前已完成了加载、验证、准备
实际影响与注意事项
这种机制带来两个关键效果:
- 性能优势:避免无谓类加载,减少内存占用和启动开销,尤其适合大量配置常量场景
-
陷阱风险:若常量值后期变更(如更新 A 类中的
NAME = "Jerry"),但调用方未重新编译,则仍显示旧值——因为值已被硬编码进调用方 class 文件 - 调试困惑:断点打在 A 类静态块里,却从不命中,往往就是因为访问的是编译期常量


















