在 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
创建独立对象)。