核心要点
在 Java 中,创建 String 对象主要有两种方式:

- 字面量赋值 (Literal Assignment)
new关键字构造 (Using thenewKeyword)
这两种方式在底层实现和内存分配上有着根本的区别。
字面量赋值
这是最常见、最简单的创建 String 的方式。
String str1 = "Hello World"; String str2 = "Hello World";
底层原理:字符串常量池
当你使用字面量创建 String 对象时,JVM(Java 虚拟机)会执行以下操作:
- 检查常量池:JVM 首先会在一个特殊的内存区域叫做字符串常量池 中查找是否存在
"Hello World"这个字符串。 - 存在则复用:如果常量池中已经存在
"Hello World",JVM 就不会创建新的对象,而是直接将引用str1指向常量池中已有的对象。str2也会指向同一个对象。 - 不存在则创建:如果常量池中没有
"Hello World",JVM 就会在常量池中创建这个字符串对象,然后将引用str1指向它。str2同样会指向这个新创建的对象。
内存示意图:

栈内存
+-------+
| str1 | --------+
+-------+ |
|
+-------+ |
| str2 | --------+
+-------+ |
|
堆内存 - 字符串常量池
+-----------------------+
| "Hello World" (唯一) |
+-----------------------+
通过字面量方式创建的多个相同的字符串,它们在内存中实际上是同一个对象,这极大地节省了内存。
String str1 = "Hello"; String str2 = "Hello"; System.out.println(str1 == str2); // 输出 true // == 比较的是两个对象的内存地址(引用),因为 str1 和 str2 指向同一个对象,所以结果为 true。
new 关键字构造
这种方式通过调用 String 类的构造函数来创建对象。
String str3 = new String("Hello World");
String str4 = new String("Hello World");
底层原理:堆内存
当你使用 new 关键字创建 String 对象时,JVM 的操作如下:
- 检查常量池:JVM 依然会先去字符串常量池中查找
"Hello World"。 - 在堆中创建新对象:
- 如果常量池中没有
"Hello World",JVM 会在常量池中创建它。 - JVM 一定会在堆内存 中创建一个新的
String对象,并用常量池中的"Hello World"来初始化这个堆中的对象。 - 如果常量池中已经有了
"Hello World",JVM 也依然会在堆内存中创建一个新的、与常量池中内容相同的String对象。
- 如果常量池中没有
内存示意图:

栈内存
+-------+
| str3 | --------+
+-------+ |
|
+-------+ |
| str4 | --------+
+-------+ |
|
堆内存
+-----------------------+
| String 对象 (str3) | 内容指向常量池的 "Hello World"
+-----------------------+
+-----------------------+
| String 对象 (str4) | 内容指向常量池的 "Hello World"
+-----------------------+
|
字符串常量池 (在堆内存的一个特殊区域)
+-----------------------+
| "Hello World" (唯一) |
+-----------------------+
每次使用 new 关键字,都会在堆内存 中创建一个新的 String 对象,即使它们的内容完全相同,这些堆中的对象是相互独立的。
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // 输出 false
// == 比较的是内存地址,str3 和 str4 是堆中两个不同的对象,所以结果为 false。
两种方式的混合使用
当字面量和 new 关键字混合使用时,情况会变得更有趣。
String str5 = "Hello"; // 1. 在常量池中创建 "Hello"
String str6 = new String("Hello"); // 2. 发现常量池有 "Hello",就在堆中创建一个新的 "Hello" 对象
内存示意图:
栈内存
+-------+
| str5 | --------+
+-------+ |
|
+-------+ |
| str6 | --------+
+-------+ |
|
堆内存
+-----------------------+
| String 对象 (str6) |
+-----------------------+
|
字符串常量池
+-----------------------+
| "Hello" (str5指向的) |
+-----------------------+
System.out.println(str5 == str6); // 输出 false // str5 指向常量池,str6 指向堆内存,是两个完全不同的对象。 System.out.println(str5.equals(str6)); // 输出 true // .equals() 方法比较的是字符串的内容,"Hello" == "Hello",所以结果为 true。
性能与最佳实践
-
优先使用字面量:在绝大多数情况下,推荐使用字面量
String s = "text";来创建字符串,因为它可以利用字符串常量池的缓存机制,避免在堆中重复创建相同内容的对象,从而提高性能并减少内存消耗。 -
何时使用
new:- 当你明确需要一个全新的、独立的
String对象时,即使内容相同。 - 在某些需要频繁修改字符串内容的场景下(例如在循环中拼接字符串),使用
new创建新的对象可能比使用StringBuilder更直观(但性能更差)。 - 在进行反射或某些需要精确控制对象创建的底层操作时。
- 当你明确需要一个全新的、独立的
-
关于字符串拼接:
- 使用 号拼接字符串时,如果编译器能确定拼接后的值是常量,它会在编译时就将其合并成一个字符串,并放入常量池。
- 如果拼接的变量中有一个或以上是运行时才能确定的变量,那么编译器会使用
StringBuilder来优化拼接过程,最终在堆内存中生成新的String对象。
// 编译时优化,等同于 String s = "HelloWorld"; String s1 = "Hello" + "World"; // 运行时拼接,使用 StringBuilder String a = "Hello"; String b = "World"; String s2 = a + b; // s2 是在堆中创建的新对象
总结表格
| 特性 | 字面量赋值 (String s = "text";) |
new 关键字 (new String("text");) |
|---|---|---|
| 创建位置 | 字符串常量池 | 堆内存 |
| 内存效率 | 高的字符串只存一份。 | 低,每次都会创建新的对象。 |
| 对象唯一性 | 相同字面量创建的字符串是同一个对象。 | 每次创建的都是新的、独立的对象。 |
| 比较 | 相同字面量创建的字符串, 结果为 true。 |
不同 new 创建的字符串, 结果为 false。 |
.equals() 比较 |
,结果取决于内容是否相同。 | ,结果取决于内容是否相同。 |
| 使用场景 | 推荐,绝大多数情况下都应使用。 | 特殊需求,如需要独立对象或进行底层操作。 |
理解这两种创建方式的区别,是掌握 Java 内存管理和编写高性能 Java 代码的重要一步。
