在 Java 中,String str1 = "hello"; 和 String str2 = "hello"; 使用 == 比较时返回 true,这与 字符串常量池(String Pool) 和 JVM 的内存管理机制 有关。下面详细解释:
1. Java 字符串的两种创建方式
(1) 直接赋值(字面量方式)
String str1 = "hello"; // 存储在字符串常量池
String str2 = "hello"; // 复用常量池的"hello"
- 字符串常量池(String Pool) 是 JVM 在 堆内存(Heap) 中开辟的一块特殊存储区域,用于存储字符串字面量(直接赋值的字符串)。
- 当使用
"hello"这样的字面量创建字符串时,JVM 会(也就是说相同的只存储一个):- 先在字符串常量池中查找是否已存在
"hello"。 - 如果存在,则直接返回该字符串的引用(不会新建对象)。
- 如果不存在,则在常量池中创建该字符串,并返回引用。
- 先在字符串常量池中查找是否已存在
因此:
str1和str2指向同一个字符串常量池对象。str1 == str2比较的是 引用地址,由于是同一个对象,返回true。
(2) 使用 new 关键字创建
String str3 = new String("hello"); // 强制在堆中新建对象
String str4 = new String("hello"); // 再新建一个对象
new String("hello")会:- 先在字符串常量池中查找
"hello"(如果不存在则创建)。 - 然后在堆内存中新建一个
String对象,并指向常量池中的"hello"(但str3和str4本身是不同的对象)。
- 先在字符串常量池中查找
- 因此:
str3 == str4返回false(不同对象)。str3.equals(str4)返回true(内容相同)。
2. 内存结构分析
示例代码
String str1 = "hello"; // 常量池
String str2 = "hello"; // 复用常量池的"hello"
String str3 = new String("hello"); // 堆中新对象,但指向常量池的"hello"
String str4 = new String("hello"); // 另一个新对象
内存模型
栈 (Stack) 堆 (Heap) 字符串常量池 (String Pool)
------- ------------- -------------------
str1 --------> String对象1 ------------> "hello"
str2 --------/ (引用常量池)
str3 --------> String对象2 ------------> "hello"
str4 --------> String对象3 ------------> "hello"
str1和str2直接指向 字符串常量池 的同一个"hello",所以==返回true。str3和str4是 堆中的不同对象,所以==返回false(即使它们的内容相同)。
3. 关键结论
| 比较方式 | str1 == str2 |
str1 == str3 |
str3 == str4 |
|---|---|---|---|
直接赋值 ("hello") |
true(同一常量池对象) |
false |
false |
new String() |
false |
false |
false(不同堆对象) |
最佳实践
- 比较字符串内容:
- 使用
equals():str1.equals(str2)(推荐)。 - 使用
equalsIgnoreCase()忽略大小写。
- 使用
- 比较引用是否相同:
- 使用
==(仅在需要判断是否是同一对象时使用,如字符串常量池优化场景)。
- 使用
4. 扩展:intern() 方法
如果希望 new String("hello") 也返回常量池的引用,可以使用 intern():
String str3 = new String("hello").intern(); // 强制放入常量池(如果已存在则返回引用)
String str4 = "hello";
System.out.println(str3 == str4); // true(现在指向同一个对象)
intern()方法会尝试将字符串放入常量池(如果已存在则直接返回引用)。
总结
==比较的是引用地址,直接赋值的字符串会复用常量池,所以str1 == str2返回true。new String()会强制创建新对象,即使内容相同,==也会返回false。- 比较字符串内容时,始终使用
equals(),避免==的误用。
这样设计是为了 节省内存(避免重复字符串占用空间),同时提供灵活性(允许 new String 创建独立对象)。