常量折叠是javac在编译期将满足三条件(操作数全为编译期常量、无运行时依赖、运算符受支持)的表达式直接替换为字面值,字节码中消除对应指令,实现跨类内联压缩与链接解耦。

Java 运算符在表达式中的计算,表面上看是运行时行为,但很多简单运算其实在编译阶段就被“提前算完”了——这就是常量折叠(Constant Folding)。它不是 JVM 运行时做的,而是 javac 在生成字节码前就完成的静态优化。理解它,关键不是看代码怎么写,而是看字节码里到底有没有那条运算指令。
哪些表达式会被折叠?看三个硬条件
只有同时满足以下全部条件,编译器才会把表达式直接替换成结果值:
-
操作数全是编译期常量:比如字面量(
10、"abc")、public static final基本类型或 String 字段(如Config.TIMEOUT = 5000),或者由它们构成的简单运算(2 * 3、"a" + "b") -
不涉及任何运行时依赖:不能调用方法(哪怕
String.valueOf(1))、不能 new 对象、不能读系统属性、不能用非 final 变量(哪怕只是final int x = 1;的局部变量,也不参与跨类折叠) -
运算符在编译器支持范围内:
+ - * / % & | ^ << >> >>> && || ?: == != < > <= >=等都支持;但注意浮点数(尤其是double)因精度问题,部分 JDK 版本可能跳过折叠
运算符 + 的特殊性:字符串拼接的两种命运
字符串的 + 是最典型的分水岭,一眼就能看出是否发生折叠:
-
全字面量或 static final String 拼接 → 编译期折叠为单个字符串,字节码里只有
ldc "hello world",无StringBuilder,无方法调用 -
只要有一个操作数是非 final 变量、方法返回值、或局部 final 变量 → 退化为运行时拼接,字节码中出现
new StringBuilder、append()、toString()
例如:String s = "a" + getHost() + "port"; 中,哪怕 getHost() 返回固定字符串,整个表达式也无法折叠——因为方法调用本身是运行时行为。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
怎么验证是不是真折叠了?别猜,看字节码
光看 Java 源码容易误判,必须用 javap -c 查看实际生成的指令:
- 如果看到
bipush 15、iconst_5、ldc "OK"—— 折叠成功,原表达式已消失 - 如果看到
getstatic Config.MAX_RETRY或invokestatic java/lang/String.valueOf—— 没折叠,运行时才访问或计算 - 特别注意:
==比较字符串时返回true,往往就是折叠生效的间接证据(两个引用指向常量池同一地址)
折叠后的影响:不只是“快一点”
它改变的是程序的链接结构和部署逻辑:
-
调用方 class 文件完全不依赖被折叠字段所在的类:删掉
Config.class,只要没其他引用,程序仍能加载运行 -
字段声明加
volatile也没用:一旦折叠,字节码里根本没字段访问,volatile的内存语义自然失效 - 字节码更小、类加载更快、运行时无字段解析开销:尤其适合配置类中大量使用常量的场景

















