String.intern() 方法解析

最近在网上看到一个很有意思的题目,如下:

String s1 = new StringBuilder("Air").append("said").toString();
System.out.println(s1.intern() == s1);// 在 JDK 1.6 或之前返回 false, 1.7 或以后返回 true

String s2 = new StringBuilder("Airsaid2").toString();
System.out.println(s2.intern() == s2);// false

为什么在不同的 JDK 版本上会有不同的结果呢?

这是因为,由于从 JDK1.7 开始,常量池从方法区移到了 Java 堆中。String.intern() 方法的实现也有了较大的改变。

在 1.7 之前,该方法会首先去字符串常量池中查询是否存在,如果存在,则直接返回常量池中的地址引用,没有,则会 copy 字符串到常量池,并返回常量池中的地址引用。而从 1.7 开始,intern() 方法会首先去字符串常量池中查询是否存在,如果存在,则直接返回常量池中的地址引用。但是,如果在常量池中找不到,则不会再将字符串 copy 到常量池,而会在常量池中生成一个对堆中原字符串的引用。

按照上面的理论再来分析分析原有的代码:

String s1 = new StringBuilder("Air").append("said").toString();

这行代码在字符串常量池中分别放入了: “Air” 和 “said” 两个字符串,并在堆内存中创建了 “Airsaid” String 对象, 并将该对象的地址返回给了 s1。 (注意此时的常量池中并没有 “Airsaid”)

System.out.println(s1.intern() == s1);// 在 JDK 1.6 或之前返回 false, 1.7 或以后返回 true

s1.intern(): 由于 intern() 方法在字符串常量池中并没有找到 “Arisaid”,于是:

  • JDK1.6 或之前:intern() 方法会将 “Airsaid” 字符串 copy 到常量池,并返回常量池中的地址。
  • JKD1.7 或以后:intern() 方法会在常量池中生成一个对堆中 “Airsaid” 的引用,并返回堆中地址。

于是在 JDK 1.6 时,s1 的引用指向的是堆中的,而 s1.intern() 指向的是常量池中的,于是结果为 false。

但是在 JDK 1.7 时,s1 和 s1.intern() 的引用都指向的是堆内存中的,于是结果为 true。

再来看看 s2 的例子:

String s2 = new StringBuilder("Airsaid2").toString();
System.out.println(s2.intern() == s2);// false

其中,new StringBuilder(“Airsaid2”) 会在常量池中创建 “Airsaid2” 的常量,并在堆内存中创建该对象,然后返回堆中的地址引用。

在执行 s2.intern() 时,由于常量池中已经存在了,那么就直接返回常量池中的引用了。

一个是堆中的,一个是常量池中的,结果肯定是 false 了。

在最后,我们通过 javap -verbose 命令看下其常量池:

Constant pool:
   #1 = Methodref          #13.#34        // java/lang/Object."<init>":()V
   #2 = Class              #35            // java/lang/StringBuilder
   #3 = String             #36            // Air
   #4 = Methodref          #2.#37         // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   #5 = String             #38            // said
   #6 = Methodref          #2.#39         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Methodref          #2.#40         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Fieldref           #41.#42        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #43.#44        // java/lang/String.intern:()Ljava/lang/String;
  #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Z)V
  #11 = String             #47            // Airsaid2
  #12 = Class              #48            // Test
  #13 = Class              #49            // java/lang/Object

可以看到,确实是没有 “Airsaid” 常量的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值