Java中String常量折叠本质是编译期优化:当拼接操作数全为编译期常量(字面量或final String/基本类型变量)、表达式仅含简单运算符、且无运行时调用时,javac直接替换为字面量并入字符串常量池。

Java 中 String 字符串连接的常量折叠优化,本质是编译器在 编译期 就把能确定结果的拼接表达式算出来,直接替换成一个字符串字面量,运行时完全不执行拼接逻辑。
什么情况下会触发常量折叠
必须同时满足三个条件:
- 参与拼接的每个操作数都是编译期常量:包括字符串字面量(如
"a")、final修饰且用字面量初始化的String变量(如final String s = "x";),或final基本类型变量(用于索引或长度等间接场景) - 整个表达式只含简单运算符:
+、&、?:等,不含方法调用、对象创建、系统属性读取等运行时行为 -
String类型是唯一被允许参与常量折叠的引用类型;其他引用类型(如StringBuilder)哪怕final也不行
典型可折叠 vs 不可折叠的例子
可折叠(编译后只剩一个字面量):
-
String s = "hello" + " " + "world";→ 编译为String s = "hello world"; -
final String a = "ab"; final String b = "c"; String s = a + b;→ 折叠为"abc",进入字符串常量池
不可折叠(运行时用 StringBuilder 拼接):
立即学习“Java免费学习笔记(深入)”;
-
String a = "ab"; String b = "c"; String s = a + b;(a和b非final) -
final String s = System.getProperty("user.name") + "test";(System.getProperty()是运行时调用) -
final String s = new Date().toString() + "log";(含对象创建和方法调用)
折叠后的影响很实际
一旦发生常量折叠,结果字符串就固化在 .class 文件的常量池里:
-
==判断会返回true(因为指向同一个常量池地址),比如"a"+"b" == "ab" - 如果修改了
public static final String VERSION = "1.0"的值,但调用方没重编译,它仍用旧值——因为值已被内联进它的字节码 - 用反射读取该字段,拿到的是编译时快照,不是当前类加载后可能被修改的值(注意:
final字段本身不能改,但通过反射可绕过,此时读到的仍是编译期值)
为什么 final String 拼接能进常量池
Java 对 String 有特殊约定:只要所有拼接项都是编译期常量,整个表达式就被视为常量表达式。编译器会把结果合成一个字符串字面量,并确保它进入字符串常量池——这和 "a"+"b" 直接写死效果一致。所以:
-
public static final String PATH = "config/" + "app.properties";→ 常量池中只有"config/app.properties"这一项 - 这种折叠与
intern()无关,是编译器自动完成的;即使不调用intern(),该字符串也已在常量池中唯一存在


















