java中被static final修饰的变量若满足public static final、基本类型或string、编译期可求值常量表达式三个条件,才会触发编译期常量替换(常量折叠),否则仍保留字段引用。

Java中被static final修饰的变量,若其值在编译期可确定(即“编译期常量”),JVM会在编译阶段将其所有引用直接替换为字面量值,这个过程叫“常量折叠”(constant folding)或“编译期常量替换”。
什么条件下会触发编译期常量替换
只有同时满足以下全部条件,该变量才被视为编译期常量,进而被内联替换:
- 声明为
public static final(访问权限不影响是否替换,但影响跨类可见性) - 类型是基本类型(如
int、boolean、char等)或String - 初始化表达式是编译期可求值的常量表达式(如字面量、常量间运算、字符串拼接等)
- 不能通过方法调用、new对象、运行时计算等方式初始化(例如
Math.max(1,2)不行,1+2可以)
典型可替换与不可替换的例子
会被替换(编译后字节码中无对该变量的字段引用):
public static final int MAX = 100; public static final String NAME = "hello" + "world"; // 编译期拼接为"helloworld" public static final boolean FLAG = true && false;
不会被替换(仍保留字段引用,运行时读取):
public static final int COUNT = new Random().nextInt(); // 运行时计算
public static final String MSG = System.getProperty("os.name"); // 方法调用
public static final int[] ARRAY = {1,2,3}; // 数组对象,非基本类型/字符串字面量
如何验证是否发生了常量替换
可通过反编译字节码观察:
- 使用
javap -c 类名查看字节码:若调用处直接出现ldc指令加载字面量(如ldc #5对应整数100),说明已被替换 - 若仍看到
getstatic指令访问该字段,则未被替换 - 修改原常量类重新编译,但不重编依赖它的其他类——如果依赖类行为未变,说明已内联;若行为改变(如打印新值),则说明未内联(仍读取原字段)
注意事项和潜在陷阱
常量替换是编译器优化,不是JVM规范强制要求,但主流JDK(javac)均实现。需注意:
- 替换发生在编译期,因此依赖类必须在常量类之后编译,或至少使用相同版本的常量值
- 若常量值变更,所有引用它的类必须重新编译,否则仍使用旧值
-
String类型仅对字符串字面量拼接有效;final String s = "a"; final String t = s + "b";中t不会被替换(因为s不是字面量) - 枚举常量、
Integer.valueOf(123)等包装类实例不参与此替换(它们不是编译期常量)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











