Java 中字符串的赋值主要有两种方式:字面量赋值 和 new 关键字赋值,这两种方式在底层机制上有根本的区别,决定了它们在内存中的表现。

字面量赋值
这是最常见、最简单的赋值方式。
String str1 = "Hello World"; String str2 = "Hello World";
工作原理:字符串常量池
当你使用双引号 创建一个字符串时,JVM(Java 虚拟机)会执行以下操作:
- 检查常量池:JVM 首先会去一个叫做 字符串常量池 的特殊内存区域中查找,是否已经存在一个内容相同的字符串对象。
- 创建或复用:
- 如果常量池中没有这个字符串,JVM 就会在常量池中创建一个新的
String对象,并将引用返回给变量str1。 - 如果常量池中已经存在这个字符串(
str2赋值时),JVM 就不会创建新的对象,而是直接将常量池中已存在的对象的引用返回给变量str2。
- 如果常量池中没有这个字符串,JVM 就会在常量池中创建一个新的
内存示意图
栈内存 堆内存
+-------+ +---------------------+
| str1 | -------------> | "Hello World" (SCP) |
+-------+ +---------------------+
| str2 | ---------------^
+-------+
- SCP (String Constant Pool):字符串常量池,它是堆内存中的一块特殊区域。
- 可以看到,
str1和str2指向的是同一个内存地址。
验证:使用 和 equals()
- 比较的是两个变量的内存地址(引用)是否相同。
equals():比较的是两个字符串对象的是否相同。
String str1 = "Hello"; String str2 = "Hello"; System.out.println(str1 == str2); // 输出: true (因为它们指向同一个对象) System.out.println(str1.equals(str2)); // 输出: true (因为它们的内容相同)
使用 new 关键字赋值
这种方式显式地告诉 JVM:“我要创建一个新的 String 对象”。
String str3 = new String("Hello");
String str4 = new String("Hello");
工作原理:堆内存
当你使用 new 关键字时,JVM 的行为完全不同:

- 忽略常量池检查:JVM 不会去检查字符串常量池,它会直接在堆内存 中分配一块新的内存空间来创建
String对象。 - 创建新对象是否相同,每次使用
new都会创建一个全新的、独立的对象。 - (可选的) 常量池驻留:值得注意的是,在
new String("Hello")这个过程中,字符串字面量"Hello"仍然会被先放入字符串常量池中。new操作会在堆内存中再创建一个副本,变量str3指向的是堆内存中的这个副本。
内存示意图
栈内存 堆内存
+-------+ +---------------------+
| str3 | -------------> | "Hello" (堆) |
+-------+ +---------------------+
| str4 | -------------> | "Hello" (堆) |
+-------+ +---------------------+
| "Hello" (SCP) | <-- 注意这个
+---------------------+
str3和str4指向的是两个不同的内存地址,尽管它们的内容相同。
验证:使用 和 equals()
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // 输出: false (因为它们指向不同的对象)
System.out.println(str3.equals(str4)); // 输出: true (因为它们的内容相同)
混合情况分析
当字面量和 new 混合使用时,情况会更有趣。
String str5 = "Hello"; // 字面量,从常量池获取
String str6 = new String("Hello"); // new,在堆内存创建新对象
System.out.println(str5 == str6); // 输出: false
System.out.println(str5.equals(str6)); // 输出: true
内存示意图
栈内存 堆内存
+-------+ +---------------------+
| str5 | -------------> | "Hello" (SCP) |
+-------+ +---------------------+
| str6 | -------------> | "Hello" (堆) |
+---------------------+
str5指向常量池中的"Hello"。str6指向堆内存中由new创建的"Hello"。- 它们是两个完全不同的对象,
str5 == str6为false。
intern() 方法
intern() 是一个特殊的方法,它可以将一个字符串“驻留”到字符串常量池中。
String str7 = new String("Hello"); // str7 在堆中
String str8 = str7.intern(); // str8 指向常量池中的 "Hello"
// str7.intern() 会返回常量池中 "Hello" 的引用
// 因为 "Hello" 字面量已经存在于常量池了
System.out.println(str7 == str8); // 输出: false
System.out.println(str5 == str8); // 输出: true (str5是字面量,也指向常量池)
intern() 的工作流程:

- 检查字符串常量池中是否已经存在与调用对象内容相同的字符串。
- 如果存在,直接返回常量池中该字符串的引用。
- 如果不存在,则将堆中的这个字符串对象复制一份到字符串常量池中,然后返回常量池中这个新对象的引用。
总结与最佳实践
| 特性 | 字面量赋值 (String s = "abc";) |
new 关键字赋值 (String s = new String("abc");) |
|---|---|---|
| 创建位置 | 字符串常量池 | 堆内存 |
| 内存效率 | 高的字符串只存一份,节省内存。 | 低,每次都创建新对象,可能造成内存浪费。 |
| 执行速度 | 稍快,直接从常量池引用,无需在堆中分配新对象。 | 稍慢,涉及在堆中分配新对象的过程。 |
| 比较 | 的字符串字面量, 结果为 true。 |
相同, 结果也为 false。 |
| 推荐场景 | 绝大多数情况下都推荐使用字面量,这是 Java 语言的设计初衷,也是最符合直觉的方式。 | 需要创建一个全新的、独立的字符串对象时(为了在多线程环境中安全地修改,但String本身不可变,所以此场景较少)。 从 IO 流(如文件、网络)中读取数据并构建字符串时。 |
核心建议:
在 Java 中,除非有特殊需求,否则请始终使用字面量来创建和赋值字符串。 这不仅能提高代码的可读性,还能带来更好的性能和内存效率。
String 的一个重要特性:不可变性,一旦一个 String 对象被创建,它的内容就不能被改变,任何看起来像“修改”字符串的操作(如 concat(), replace(), substring() 等),实际上都是创建了一个新的 String 对象来保存修改后的结果,而原始对象保持不变。
