String常量池和new Stirng(“abc”)和String str=“abc”;的区别,在什么情况下会得到相等。是不是String str=“abc”;在编译时期就确定了值。
非常好的问题 👍,这其实是理解 Java 字符串底层机制的关键点之一。
我们来系统地讲清楚这三者的区别与相等条件 👇
🧩 一、String 常量池(String Constant Pool)
在 Java 中,字符串有一个特殊的“常量池”机制(String Pool),
用于节省内存并提高字符串比较效率。
📌 所有在编译期就能确定值的字符串常量,都会被放进常量池(位于堆中的一块特殊区域)。
🧠 二、三种创建方式的区别
| 写法 | 是否在常量池中 | 是否创建新对象 | 示例解释 |
|---|---|---|---|
String str = "abc"; | ✅ 是 | ❌ 否(如果池中已有则重用) | 编译期直接把 "abc" 放入常量池,变量 str 指向它 |
new String("abc"); | ✅("abc"常量)+ ✅(堆中新对象) | ✅ 是 | 会先确保 "abc" 在常量池中,再在堆上创建一个新对象 |
"abc" | ✅ 是 | ❌ 否 | 表示常量池中的字面量对象本身 |
📘 示例分析
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
内存结构如下:
常量池:"abc"(一个对象)
堆区:new String("abc")(一个新对象)
| 比较 | 结果 | 原因 |
|---|---|---|
s1 == s2 | ✅ true | 都指向常量池中的同一个对象 |
s1 == s3 | ❌ false | s3 是堆中新建的对象 |
s1.equals(s3) | ✅ true | 内容相同 |
⚙️ 三、什么时候会“相等”
只有在以下几种情况下,字符串对象才会共享或相等(引用相同):
✅ 1. 直接用字面量定义
String a = "abc";
String b = "abc";
System.out.println(a == b); // true
编译器会把
"abc"放进常量池,只保留一份。
✅ 2. 使用 intern() 方法
intern() 方法会把堆中的字符串放入常量池(如果池中没有的话),然后返回池中的引用。
String s1 = new String("abc");
String s2 = s1.intern();
String s3 = "abc";
System.out.println(s2 == s3); // ✅ true
System.out.println(s1 == s3); // ❌ false
✅ 3. 编译期能确定的字符串拼接
编译器在编译时会对常量表达式进行优化折叠:
String s1 = "ab" + "c"; // 编译期优化为 "abc"
String s2 = "abc";
System.out.println(s1 == s2); // ✅ true
但是,如果拼接中有变量,就不同了👇
String x = "ab";
String s1 = x + "c"; // 运行时拼接
String s2 = "abc";
System.out.println(s1 == s2); // ❌ false
因为 x + "c" 会在运行时创建新的 StringBuilder 对象拼接后再 toString(),结果在堆上。
⚡ 四、是的,字面量确实是编译期确定的
✅ 你说得完全正确 ——
String str = "abc";这种形式的字符串在 编译阶段就已经确定值,
编译器会在.class文件的常量池中存储"abc",
加载类时 JVM 会把它放入运行时的 字符串常量池(String Pool)。
🧾 五、总结对比表
| 创建方式 | 存放位置 | 是否共用 | 是否编译期确定 | 举例 | 备注 |
|---|---|---|---|---|---|
"abc" | 常量池 | ✅ | ✅ | 直接常量 | 编译期常量 |
String s = "abc"; | 常量池 | ✅ | ✅ | 直接引用池中对象 | |
new String("abc") | 堆 + 常量池 | ❌ | ✅ | 先保证常量池中有 "abc",再堆中新建 | |
"ab" + "c" | 常量池 | ✅ | ✅ | 编译期优化 | |
x + "c" | 堆 | ❌ | ❌ | 运行期拼接创建新对象 | |
new String("abc").intern() | 常量池 | ✅ | ✅ | intern() 返回池中引用 |
编译期直接把 “abc” 放入常量池,变量 str 指向它——这里常量池中的也是对象吗?
非常好的追问 ✅,这正是很多人容易模糊的地方。
答案是——
💡 是的,常量池中的字符串也是对象(
String对象),
但它与普通new String()创建的对象在“存储位置”和“生命周期”上有区别。
我们来分层解释👇
🧩 一、String 常量池中保存的是什么?
在 Java 中,所有字符串(包括常量池中的)本质上都是 java.lang.String 对象。
也就是说:
String s = "abc";
这句代码做了两件事:
- JVM 检查字符串常量池中是否已经有
"abc"对象; - 如果没有,就在堆中创建一个
String对象来表示"abc",然后把它的引用放进常量池; - 最后让变量
s指向这个常量池中的引用。
🧠 换句话说:
- 常量池里保存的不是“原始字符”,而是指向堆中
String对象的引用; "abc"自身仍然是一个堆对象;- 区别在于:常量池记录并复用这份对象的引用。
⚙️ 二、JVM 的内存层次理解(图解)
举个例子:
String s1 = "abc";
String s2 = "abc";
内存示意:
┌────────────────────────────┐
│ 字符串常量池 (String Pool) │
│ ┌────────────────────┐ │
│ │ "abc" ───────────┼──┼──→ (堆) String对象 ["a","b","c"] │
│ └────────────────────┘ │
└────────────────────────────┘
↑
s1, s2
说明:
- “常量池”存储的是对该
String对象的引用; - 这个
String对象本身在堆中; - 多个相同字面量的字符串(
"abc") 会共享这同一个堆中对象。
🧠 三、和 new String("abc") 的区别
String s1 = "abc";
String s2 = new String("abc");
内存情况:
常量池:
"abc" ─────┐
│
堆: │
[String对象1] │
[String对象2] ←─ new String("abc")
区别:
"abc"(字面量)在类加载时放入常量池;new String("abc")会在堆上重新创建一个新的String对象;- 所以
s1 == s2是false,虽然equals()为true。
⚡ 四、从 JVM 实现角度看
JVM 在加载 .class 文件时,会解析常量池(Constant Pool Table)中的字符串常量项(CONSTANT_String_info),
并在运行时通过 String.intern() 机制,把它注册进运行时的字符串常量池。
所以:
- 编译期:只是在
.class文件中记录"abc"; - 运行期:类加载时才会创建对应的堆中
String对象,并放入常量池引用表。
✅ 五、总结一句话
| 概念 | 是否对象 | 存放位置 | 内容 | 生命周期 |
|---|---|---|---|---|
"abc" 字面量 | ✅ 是 String 对象 | 堆 | "a","b","c" | 随 JVM 存活 |
| 常量池条目 | ❌ 不是对象本身(是引用) | 常量池区 | 指向堆中 "abc" | 与类加载器同生命周期 |
new String("abc") | ✅ 新建对象 | 堆 | "a","b","c" | 方法结束后可回收 |
🟢 一句话总结:
字符串常量池中保存的是指向堆中
String对象的引用;
"abc"自身仍然是一个String对象,只不过这份对象被“登记”进了常量池以供重用。

被折叠的 条评论
为什么被折叠?



