Java中使用+或concat()拼接字符串时,新String对象的创建位置疑问
嘿,这个问题真的戳中了Java字符串的核心知识点——常量池和堆的区别,很多刚入门的小伙伴都会在这里卡壳,我来给你一步步讲明白~
先明确你的代码里各变量的情况
先回顾下你给出的代码:
String str1 = "Hello"; String str2 = "World"; String str3 = new String("HelloWorld"); String str4 = str1 + str2;
你已经知道:
str1、str2指向字符串常量池里的"Hello"、"World"对象str3在**堆内存(常量池外)**创建新对象,同时常量池里会生成"HelloWorld"(因为字面量"HelloWorld"触发了常量池存储)
关于+拼接的执行逻辑
这里要分两种核心情况:
1. 拼接的是编译期常量
如果是直接用字面量或者final修饰的常量拼接,比如:
final String str1 = "Hello"; final String str2 = "World"; String str4 = str1 + str2; String str5 = "HelloWorld"; System.out.println(str4 == str5); // 输出true
这时候编译器会在编译阶段直接把str1 + str2优化成"HelloWorld",所以str4直接指向常量池里的对象,和str5的引用完全相同。
2. 拼接的是非编译期常量(普通变量)
这就是你代码里的情况:str1和str2是普通变量,编译器无法在编译期确定它们的最终值。此时Java会把str1 + str2转换成如下逻辑:
String str4 = new StringBuilder().append(str1).append(str2).toString();
而StringBuilder.toString()的底层实现是直接调用new String(value, 0, count)创建新对象,这个对象是在**堆内存(常量池之外)**的,不会自动进入字符串常量池。所以str4和常量池里的"HelloWorld"引用完全不同。
关于concat()方法的执行逻辑
str1.concat(str2)的底层逻辑也很清晰:
- 先计算拼接后的总长度,如果总长度为0,直接返回原字符串
- 如果长度大于0,会创建一个新的char数组,把两个字符串的内容复制进去
- 最后通过
new String(result, 0, totalLength)创建新对象,同样是在堆内存中,不会自动入池。
为什么不用new却在池外创建对象?
这个问题的核心是:变量拼接/concat的结果是运行时动态生成的,Java不会自动把这种运行时生成的字符串放进常量池(只有编译期确定的字面量才会自动入池)。
虽然你没有手动写new,但拼接操作的底层API(比如StringBuilder.toString()、String.concat())已经帮你调用了new String(),只是这个过程被封装起来了,不需要你手动编写而已。
如果想让运行时生成的字符串进入常量池,可以手动调用intern()方法:
String str4 = (str1 + str2).intern(); String str5 = "HelloWorld"; System.out.println(str4 == str5); // 输出true
intern()方法会检查常量池里是否有相同内容的字符串,如果有就返回池里的引用;如果没有,就把当前字符串的内容放进常量池并返回引用。
内容的提问来源于stack exchange,提问作者404 Brain Not Found

