Java中String常量折叠是编译期静态替换,需同时满足:所有操作数为编译期常量、仅含简单运算符、仅限String类型;满足时字节码直接生成ldc指令加载合并后字符串。

Java 中 String 常量折叠不是“运行时优化”,而是编译器在生成字节码前就完成的静态替换——只要满足条件,拼接动作根本不会出现在运行期。
哪些拼接能被编译期折叠
必须同时满足三个硬性条件:
- 所有参与拼接的操作数都是编译期常量:包括字符串字面量("a")、final String 变量(且用字面量或常量表达式初始化),以及 final 基本类型变量(如 final int port = 8080;)
- 表达式只含简单运算符:+、&&、||、三元运算符等,不能调用任何方法(如 System.getProperty())、不能创建对象、不能访问非 final 字段
- 仅限 String 类型:其他引用类型(哪怕 final 修饰的 StringBuilder)不参与该机制
折叠后的真实效果
编译后,字节码里不会出现 StringBuilder、append 或 toString 调用,只剩一条 ldc 指令加载合并后的完整字符串:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- String s = "Hello" + " " + "World"; → 编译为 String s = "Hello World";
- final String prefix = "v"; final String ver = "1.0"; String version = prefix + ver; → 折叠成 "v1.0",直接进入字符串常量池
- 结果字符串在 .class 文件中已固化,多个相同表达式会指向同一个常量池地址,所以 "a"+"b" == "ab" 返回 true
容易踩坑的“伪常量”场景
看似是常量,但因违反任一条件而无法折叠,退化为运行时 StringBuilder 拼接:
- 变量未用 final 修饰:String a = "x"; String b = a + "y"; → 运行时 new StringBuilder()
- final 变量初始化含运行时逻辑:final String now = new Date().toString(); → 不可折叠
- 拼接中混入系统属性:final String home = System.getProperty("user.home") + "/config"; → 调用方法,跳过折叠
- 使用非字面量的 char 数组或 byte 数组构造:new String(new byte[]{97, 98}) → 对象创建,不参与常量表达式计算
怎么写才真正触发常量折叠
目标是让 javac 看到“确定无疑的结果”,推荐写法:
- 配置类中统一用 public static final String 定义路径、SQL 片段、HTTP 头等固定内容
- 模板字符串优先用字面量拼接:String sql = "SELECT * FROM user WHERE id = " + "?";(注意:? 是字面量,不是变量)
- 需要组合数字时,用 final 基本类型:final int CODE = 200; String msg = "HTTP/" + CODE; → 可折叠为 "HTTP/200"
- 避免在拼接表达式中引入任何外部依赖、方法调用或运行时状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










