基础篇三 一行 new String(“hello“) 到底创建了几个对象?90% 的人答错了

文章目录

个人网站

这道题看似简单,实则是 Java 内存模型的试金石。很多面试者脱口而出"两个",但答案远没有这么简单------"几个"取决于上下文,而能说清上下文的人,才是真正理解 JVM 内存模型的人。

一、先说结论:1 个或 2 个

java 复制代码
String s = new String("hello");
  • 如果字符串常量池中已经存在 "hello" → 创建 1 个对象(堆上的 String 实例)
  • 如果字符串常量池中不存在 "hello" → 创建 2 个对象(常量池中的字面量 + 堆上的 String 实例)

为什么会有这种不确定性?因为 "hello" 这个字面量在编译期就被确定了,而它是否入池取决于在这行代码执行之前,常量池中是否已经有了 "hello"

二、拆解过程:这行代码到底做了什么?

第一步:处理字面量 "hello"

当 JVM 遇到 "hello" 这个字面量时,会先去字符串常量池中查找:

  • 找到了 → 直接返回引用,不做任何创建
  • 没找到 → 在常量池中创建一个 "hello" 字符串对象,然后返回引用

第二步:执行 new String()

new 关键字一定会在堆内存 上创建一个新的 String 对象,用常量池中 "hello" 的值来初始化。

java 复制代码
String s = new String("hello");

用伪代码描述整个过程:

复制代码
1. 查找常量池是否有 "hello"
   - 没有 → 常量池创建 "hello" 对象(对象 A)
   - 有   → 跳过

2. 堆上 new 一个 String 对象(对象 B),值为 "hello"

3. s 指向对象 B(堆上的对象)

所以,对象 A 是常量池中的,对象 B 是堆上的,它们是两个独立的对象,只是值相同。

三、用图理解内存布局

场景一:常量池中不存在 "hello"(首次出现)

复制代码
字符串常量池                        堆
┌──────────────┐              ┌──────────────┐
│ "hello" (A)  │              │ "hello" (B)  │
└──────────────┘              └──────────────┘
                                     ↑
                                     │
                                 s 指向 B

创建了 2 个对象

场景二:常量池中已存在 "hello"

复制代码
字符串常量池                        堆
┌──────────────┐              ┌──────────────┐
│ "hello" (A)  │  (之前已存在)│ "hello" (B)  │
└──────────────┘              └──────────────┘
                                     ↑
                                     │
                                 s 指向 B

创建了 1 个对象

四、什么情况下常量池会提前有 "hello"?

只要在此之前有代码触发了 "hello" 的入池,常量池中就会存在:

java 复制代码
// 情况 1:直接使用字面量
String a = "hello";               // "hello" 入池
String b = new String("hello");   // 常量池已有,只创建 1 个

// 情况 2:同一个类中前面的代码已经用过
System.out.println("hello");      // "hello" 入池
String s = new String("hello");   // 常量池已有,只创建 1 个

// 情况 3:intern() 方法
String c = new String("hel") + new String("lo");
c.intern();                        // "hello" 入池
String d = new String("hello");   // 常量池已有,只创建 1 个

五、一个更复杂的变体

java 复制代码
String s = new String("hello") + new String("world");

这行代码创建了几个对象?逐步拆解:

步骤 操作 创建的对象
1 字面量 "hello" 入池 常量池 "hello"(如果不存在)
2 new String("hello") 堆上 String 对象
3 字面量 "world" 入池 常量池 "world"(如果不存在)
4 new String("world") 堆上 String 对象
5 + 拼接操作 底层用 StringBuilder,最终 toString() 创建堆上 String 对象

最坏情况:5 个对象(2 个常量池 + 2 个 new + 1 个拼接结果)

注意:StringBuilder 本身也是对象,但它是中间过程,通常不计入"这行代码创建了几个对象"的答案中。

六、intern() 方法:手动入池

intern() 是 String 类的一个 native 方法,作用是:

  • 如果常量池中已存在该字符串,返回常量池中的引用
  • 如果不存在,将该字符串放入常量池,并返回引用
java 复制代码
String s1 = new String("hello");   // 堆上对象
String s2 = s1.intern();           // 返回常量池中的 "hello"
String s3 = "hello";               // 常量池中的 "hello"

System.out.println(s1 == s2);  // false(s1 在堆,s2 在常量池)
System.out.println(s2 == s3);  // true(都指向常量池中的同一个对象)

intern 的经典面试题(JDK 7+)

java 复制代码
String s1 = new String("a") + new String("b");
// 此时常量池中有 "a" 和 "b",但没有 "ab"
// s1 指向堆上的 "ab" 对象

s1.intern();
// JDK 7+:常量池中存储的是堆上 "ab" 对象的引用(不需要再拷贝一份)

String s2 = "ab";
// "ab" 在常量池中已存在(是 s1 的引用),所以 s2 也指向同一个对象

System.out.println(s1 == s2);  // JDK 7+ : true
                                // JDK 6  : false(常量池在永久代,存的是拷贝)

七、JDK 6 vs JDK 7+:常量池的位置变化

这是理解 String 内存模型的关键背景:

版本 常量池位置 intern() 行为
JDK 6 及之前 永久代(PermGen) 把字符串复制一份到永久代
JDK 7 及之后 堆(Heap) 把堆上对象的引用存入常量池

这个变化直接导致了 s1 == s2 在不同 JDK 版本下结果不同。JDK 7 以后,常量池不再拷贝字符串内容,而是直接记录引用,避免了重复存储。

八、面试速答模板

Q:new String("hello") 创建了几个对象?

A:1 或 2 个。如果常量池中已有 "hello",只在堆上创建 1 个 String 对象;如果常量池中没有 "hello",会先在常量池创建 1 个,再在堆上 new 1 个,共 2 个。关键点是 new 一定会在堆上创建新对象,而字面量是否入池取决于之前是否已经出现过。
Q:String s = new String("a") + new String("b") 创建了几个对象?

A:最多 5 个------常量池中 "a" 和 "b" 各 1 个(如果不存在),堆上 new String("a")new String("b") 各 1 个,拼接结果的堆上对象 1 个。拼接底层使用 StringBuilder 的 toString() 方法。
Q:intern() 方法的作用?

A:将字符串放入常量池。如果常量池已有则返回常量池引用;如果没有,JDK 6 会复制字符串到永久代,JDK 7+ 会记录堆上对象的引用。这也是为什么 JDK 7+ 中 new String("a")+new String("b") 后调用 intern,再用字面量赋值,两个引用会指向同一个对象。

相关文章

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

相关推荐
青衫码上行2 小时前
【从零开始学习JVM】栈中存的是指针还是对象 + 堆分为哪几部分
java·jvm·学习·面试
默 语2 小时前
Java的“后路“:不是退场,而是换了一种活法
java·开发语言·python
ywf12152 小时前
Spring aop 五种通知类型
java·前端·spring
慕容卡卡2 小时前
你所不知道的RAG那些事
java·开发语言·人工智能·spring boot·spring cloud
Lyyaoo.2 小时前
【JAVA基础面经】List(Vector+ArrayList+LinkedList)
java·开发语言·list
ch.ju2 小时前
Java程序设计(第3版)第二章——if if else else if
java
SimonKing2 小时前
144K Star的开源神器,OpenCode进阶使用全攻略
java·后端·程序员
前端缘梦2 小时前
深入理解React Fiber架构:渲染流程与双缓冲机制全解析
前端·react.js·面试
程途知微2 小时前
Java线程池运行机制与拒绝策略底层全解析
java·后端