最近在网上看到一个很有意思的题目,如下:
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” 常量的。