
String.intern() 会将字符串实例注册到运行时常量池(字符串池),若池中已存在相同内容的字符串,则返回池中引用;调用后,通过字面量创建的同值字符串将与之共享同一对象,从而使 == 比较返回 true。
`string.intern()` 会将字符串实例注册到运行时常量池(字符串池),若池中已存在相同内容的字符串,则返回池中引用;调用后,通过字面量创建的同值字符串将与之共享同一对象,从而使 `==` 比较返回 `true`。
在 Java 中,== 比较的是两个引用是否指向同一内存地址,而 String.intern() 的核心作用正是统一字符串实例的内存归属——它确保内容相同的字符串在运行时仅保留一份(位于字符串池中),从而为引用相等提供基础。
我们来看原始示例的关键逻辑:
public static void main(String[] args) {
String s1 = new String("12") + new String("21"); // 在堆上创建新对象 "1221"
s1.intern(); // 将堆中 "1221" 注册进字符串池(若池中尚无该字符串)
String s2 = "1221"; // 从字符串池中直接获取(JVM 保证字面量自动 intern)
System.out.println(s1 == s2); // true ✅
}
⚠️ 注意:s1.intern() 的返回值虽未被赋值,但其副作用不可忽略——它会将 s1 所指的 "1221" 对象显式加入字符串池(前提是池中尚不存在内容相同的字符串)。而 String s2 = "1221" 是编译期确定的字面量,在类加载时会触发 ldc 指令,JVM 会先查字符串池;若已存在(如刚被 s1.intern() 加入),则直接复用该引用。
那么为什么去掉 s1.intern() 就变成 false?
因为此时字符串池中尚未存在 "1221" 的堆对象引用(尽管 "1221" 字面量本身存在于 class 文件的常量池中,但 JVM 不会在类加载时自动将所有字面量预加载进运行时常量池)。只有当字面量首次被 ldc 指令使用时,JVM 才会尝试将其解析并放入字符串池——但该过程依赖于“池中尚无同值字符串”。而本例中,s1 是运行时拼接生成的堆对象,其内容 "1221" 并未提前注册,因此 s2 = "1221" 会新建并放入字符串池一个新对象(注意:这是 JDK 7+ 行为,字符串池已移至堆中,不再是永久代);此时 s1(堆)与 s2(池)指向不同对象,== 自然为 false。
✅ 正确理解要点:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 字符串池是运行时动态维护的哈希表(
StringTable),不是编译期静态映射; - 字面量
"1221"的加载时机是首次ldc指令执行时,且以字符串池中是否存在同值字符串为前提决定是否新建; -
intern()的语义是:“确保该字符串在池中存在,并返回池中引用”,即使忽略返回值,其注册动作仍生效; - JDK 7 起,字符串池位于堆内存,
intern()可以将堆中已有字符串对象“登记”进池,后续字面量可复用该对象引用(而非复制)。
? 实用建议:
- 避免依赖
==比较字符串内容,始终使用equals(); -
intern()适用于大量重复字符串场景(如解析 CSV 的列名、HTTP 头字段),可显著降低内存占用; - 但需警惕
intern()的线程安全开销(内部加锁)及潜在内存泄漏风险(长期持有堆对象引用)。
总结:s1.intern() 改变行为的本质,是它抢先将堆中 "1221" 注册进字符串池,使后续 s2 = "1221" 直接复用该引用,最终让 s1 和 s2 指向同一对象——这正是 == 返回 true 的根本原因。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










