字符串字面量拼接在编译期完成常量折叠,如"a"+"b"+"c"直接生成"abc"存入常量池;含变量则编译为stringbuilder调用,循环中滥用+=性能差,null参与拼接转为"null"需警惕。

字符串字面量拼接在编译期就完成了
Java 编译器对 String 字面量的拼接(即全由双引号包围的字符串直接用 + 连接)会做常量折叠(constant folding),结果直接存入 class 文件的常量池。比如 "a" + "b" + "c" 在编译后等价于单个常量 "abc",不会生成任何运行时拼接逻辑。
这种优化仅适用于**所有操作数都是编译期常量**的情况——包括字符串字面量、final 修饰的基本类型或字符串变量(且初始化值也是字面量)。
-
final String s = "x"; String t = s + "y";→ 编译期优化(t指向常量池中的"xy") -
String s = "x"; String t = s + "y";→ 不优化,运行期执行(生成StringBuilder) -
final StringBuilder sb = new StringBuilder();→ 不算常量,哪怕final也不参与折叠
含变量的字符串拼接默认走 StringBuilder(JDK 8+)
只要 + 表达式中任意一端不是编译期常量,Javac 就会在字节码中插入 StringBuilder 的构造、append 和 toString 调用。这是 JDK 5 引入、并在 JDK 8 后稳定下来的实现策略。
注意:这个转换发生在编译阶段,不是 JVM 运行时 JIT 优化。你写的 a + b + c,无论变量类型是 String 还是 int,都会被翻译成类似:
立即学习“Java免费学习笔记(深入)”;
new StringBuilder().append(a).append(b).append(c).toString()
常见误区:
- 以为
String.concat()比+快 —— 实际上concat只对两个字符串有效,且不复用缓冲区;而+编译后是StringBuilder,多段拼接更稳 - 在循环里写
s += "x"—— 每次都新建StringBuilder,性能差;应显式用StringBuilder外提
String.valueOf(null) 不抛 NPE,但字符串拼接中 null 会变 "null"
这是容易踩坑的行为:Java 规定任何对象参与字符串拼接时,都会先调用 String.valueOf(obj)。而 String.valueOf(null) 返回字面量 "null",不是 NullPointerException。
所以以下代码不会崩溃,但结果可能出人意料:
String a = null; String b = "prefix" + a + "suffix"; // 结果是 "prefixnullsuffix"
如果你依赖 null 值触发空检查或默认逻辑,这种隐式转串会掩盖问题。建议:
- 对可能为
null的变量,显式用Objects.toString(a, "default") - 日志或模板拼接场景,优先用
MessageFormat或现代方案如String.format()/TextBlock(JDK 15+) - 静态分析工具(如 SpotBugs)能检测
+=循环和隐式null转换,建议开启
字符串拼接性能关键看上下文,不是只盯“+”符号
很多人纠结“该用 + 还是 StringBuilder”,其实真正影响性能的是拼接发生的频次、位置和生命周期。一个方法内拼接 3 次字符串,用 + 完全没问题;但在 for 循环内反复 +=,就是典型反模式。
JDK 9 引入了 StringConcatFactory,底层用 invokedynamic 延迟到运行时选择最优策略(比如小字符串走数组拷贝,大字符串走 StringBuilder),但这个机制对开发者透明——你不需要改写法,只要别在循环里滥用 + 即可。
真正要留意的边界情况:
- Log 拼接:SLF4J 的
logger.debug("user={} status={}", user, status)是延迟求值,比"user=" + user + " status=" + status更安全高效 - JSON 构建:不要用字符串拼接生成 JSON,极易注入/转义错误,必须用 Jackson/Gson 等库
- SQL 拼接:同理,永远用
PreparedStatement,字符串拼接 SQL 是严重安全漏洞
编译期优化再聪明,也救不了语义错误。拼接逻辑一旦脱离简单日志或路径组装,就该换更合适的抽象。











