Java8中String intern()与==的异常现象及技术疑问
Java 8下String intern()方法的异常现象解惑
先看两段核心测试代码:
// 代码1:调用intern() public static void main(String[] args) { String s1 = new String("12") + new String("21"); s1.intern(); String s2 = "1221"; System.out.println(s1 == s2); // 输出true } // 代码2:未调用intern() public static void main(String[] args) { String s1 = new String("12") + new String("21"); // s1.intern(); String s2 = "1221"; System.out.println(s1 == s2); // 输出false }
两段代码仅差s1.intern()的调用,但执行结果完全不同,以下针对你的疑问逐一解答:
疑问解答
1. s1和s2实际指向的对象是什么?
- 代码1场景:
s1指向的是通过StringBuilder.toString()在堆上新建的"1221"实例;s2指向的是字符串常量池中的引用,而这个引用就是s1对应的堆实例的地址(因为intern()把堆中该实例的引用存入了常量池)。
- 代码2场景:
s1同样指向堆上新建的"1221"实例;s2指向的是字符串常量池中新创建的"1221"实例(类加载阶段并未将"1221"入池,首次通过字面量赋值触发了入池操作)。
2. 为何调用intern()(未使用返回值)会改变执行结果?
核心误区在于:字节码常量池中的"1221"并不会在类加载时自动进入字符串常量池。
Java 8中字符串常量池存放在堆中,只有字符串被主动使用时才会触发入池:
- 字面量
"12"、"21"因为直接作为new String()的参数,属于主动使用,类加载时会被存入字符串常量池; - 而
"1221"只是字节码常量池中的符号引用,代码中未直接通过字面量引用它时,不会自动进入字符串常量池。
代码1执行流程:
s1指向堆上新建的"1221"实例;- 调用
s1.intern()时,字符串常量池中无"1221",因此将s1对应的堆实例的直接引用存入常量池; - 后续
s2 = "1221"时,发现常量池中已有该字符串的引用,直接返回这个引用(即s1的地址),因此s1 == s2为true。
代码2执行流程:
s1指向堆上新建的"1221"实例;- 未调用
intern(),字符串常量池中无"1221"; - 执行
s2 = "1221"时,JVM会在字符串常量池中新建一个"1221"实例,s2指向这个常量池实例,和s1的堆实例地址不同,因此s1 == s2为false。
对假设的验证
- 字符串常量池的入池时机:
字节码常量池中的符号引用不会自动全部进入字符串常量池,只有当字符串被主动触发入池操作时才会进入(比如直接使用字面量赋值、调用intern()方法等)。JVM规范明确,字符串字面量的入池是在首次使用时完成,而非类加载阶段。 - intern()的行为:
Java 8中,当调用intern()且常量池中无对应字符串时,会将堆中该字符串对象的直接引用存入常量池,而非复制对象。因此s2 = "1221"直接返回这个引用,和s1指向同一个对象,不存在"引用的引用"——常量池里存的就是对象的直接地址,和s1的引用值完全相同。
内容的提问来源于stack exchange,提问作者DracoYu
相关产品推荐
相关产品推荐

