基础篇三 一行 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,再用字面量赋值,两个引用会指向同一个对象。

相关文章

原文阅读


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

相关推荐
东风微鸣18 小时前
AWS 可靠性最佳实践:从架构设计到故障恢复一把梭
java·jvm·aws
略知java的景初18 小时前
【面试特集】Redis 面试题与应用场景
redis·面试·职场和发展
敲敲千反田18 小时前
微服务基础
java·微服务·架构
ideal-cs18 小时前
总结:生产环境Logback日志配置模板与pattern格式案例
java·log4j·logback·pattern·后端日志
ooseabiscuit18 小时前
Laravel3.x核心特性全解析
java·数据库·spring
凤山老林18 小时前
慢SQL治理:索引优化实战指南——从定位到优化的完整解决方案
java·sql·springboot·慢sql治理·sql 性能优化
Aision_1 天前
从工具调用到 MCP、Skill完整学习记录
java·python·gpt·学习·langchain·prompt·agi
zc.z1 天前
JAVA实现:纯PCM格式音频转换成BASE64
java·音视频·pcm
mask哥1 天前
力扣算法java实现汇总整理(上)
java·算法·leetcode
yuzhiboyouye1 天前
web前端英语面试
前端·面试·状态模式