You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中两种字符串初始化的差异及JDK1.8 intern()执行结果疑问

理解new String("X")与new String("X") + new String("Y")的差异及intern()行为

一、两种字符串初始化的核心区别

先把这两种初始化方式的底层逻辑拆解清楚:

1. new String("X")的执行过程

当你写这句代码时,JVM会做两件核心事:

  • 首先检查字符串常量池(JDK1.7+已移到堆内存中)中是否存在"X":
    • 如果不存在,先在常量池里创建一个"X"的String实例;
    • 如果已经存在,直接复用常量池里的"X";
  • 然后在堆内存中创建一个全新的String对象,这个对象的底层字符数组(JDK1.8中是char[] value)会指向常量池里"X"的字符数组(JDK1.7+优化了内存,不再拷贝数组)。
    简单说,这句代码最少创建1个对象(常量池已有"X"时),最多创建2个对象(常量池无"X"时)。

2. new String("X") + new String("Y")的执行过程

这个拼接表达式的底层是靠StringBuilder实现的,步骤更复杂:

  • 先处理两个new String实例:各自会在堆创建String对象,同时常量池会生成"X"和"Y"(如果之前没有的话);
  • JVM会隐式创建一个StringBuilder对象,依次append两个String对象;
  • 最后调用StringBuilder.toString()生成一个全新的堆String对象,注意:这个拼接后的字符串(比如"XY")不会自动进入字符串常量池;
    所以这个操作会创建至少4个对象:两个堆String对象、一个StringBuilder对象、最终拼接后的堆String对象,常量池里只有"X"和"Y",没有拼接结果。

二、你的代码在JDK1.8下的结果解析

先看你贴的代码:

public static void main(String[] args) {
    String s1 = new String("aa");
    s1.intern();
    String s2 = "aa";
    System.out.println(s1 == s2); //JDK1.6输出false,JDK1.8输出true
    String str1 = new String("str") + new String("01");
    str1.intern();
    String str2 = "str01";
    System.out.println(str1 == str2);
}

你原本以为堆对象和常量池对象用==比较会返回false,这其实是混淆了JDK1.6和JDK1.7+中intern()方法的行为差异,下面逐个分析:

1. 第一段:s1 == s2的结果差异

先明确intern()的核心变化:

  • JDK1.6及以前:字符串常量池在永久代(和堆内存物理隔离),intern()会把堆中字符串的拷贝放入常量池,返回常量池的新对象引用;
  • JDK1.7及以后:字符串常量池移到堆内存中,intern()不再拷贝对象,而是把堆中字符串的引用直接放入常量池(如果常量池里没有equals匹配的字符串)。

现在看JDK1.8的执行流程:

  • 执行String s1 = new String("aa"):
    类加载阶段,字面量"aa"已经被放入常量池,所以JVM直接在堆创建s1对象,s1指向堆内存,常量池里已有"aa"实例;
  • 执行s1.intern():
    因为常量池里已经有"aa",所以intern()直接返回常量池的"aa"引用,但我们没有把这个返回值赋值给s1,所以s1还是指向堆对象;
    (注:按这段代码的逻辑,JDK1.8下这里实际应该输出false,你提到的true可能是代码场景存在差异,比如"aa"是运行时动态生成而非类加载阶段的字面量)

2. 第二段:str1 == str2返回true的原因

这才是intern()在JDK1.8下的典型表现:

  • 执行String str1 = new String("str") + new String("01"):
    拼接后的"str01"是StringBuilder.toString()生成的堆对象,此时常量池里没有"str01";
  • 执行str1.intern():
    JVM检查常量池,发现没有"str01",于是直接把堆中str1的引用放入常量池;
  • 执行String str2 = "str01":
    JVM去常量池找"str01",发现已经存在的是str1的堆引用,所以str2直接指向str1所在的堆对象;
  • 最终str1 == str2比较的是同一个对象的引用,所以返回true。

三、总结你的误区

你之前认为堆对象和常量池对象一定是分离的,这是JDK1.6的老逻辑。JDK1.7+优化了字符串常量池的实现:

  • 常量池不再存储String对象本身,而是存储对象的引用;
  • 当intern()处理一个堆中独有的字符串时,会把堆对象的引用直接放到常量池,让后续的字面量引用指向这个堆对象,从而出现堆对象和常量池引用指向同一实例的情况。

内容的提问来源于stack exchange,提问作者Lebecca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:11:54