PS : String 分析 里已经有了,觉得有必要单独提出来…


看网上的介绍,对于字符串常量池中到底保存的是字符串对象,还是字符串对象的引用,众说纷纭…

看 jdk1.8 对 intern() 的说明.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object)method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

这个说明对于理解原理貌似没有啥参考价值…

jdk6 和 jdk6+ 的描述是一样的,但是 jdk6 和 jdk6+ 的实现并不一致.

尝试跟踪源码.

public native String intern();

是个本地方法,继续深入 :

在线查看

\openjdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}

日…到这步跟不下去了.还是搜索一下看看别人有没有关于这玩意儿的资料吧…

果然找到!!

深入解析String#intern

还是直接给结果吧…

看段代码:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

jdk6 输出 : false false
jdk7 输出 : false true

why?

  • jdk6

上面已经提到过,String s = new String("1"); 这种方式创建字符串实际生成了两个字符串对象.

首先,构造器中传入了一个字符串对象 “1”,它就被放在字符串常量池中.

然后在堆中创建一个字符串对象,s 指向这个字符串对象的首地址.

调用 s.intern() ,字符串常量池已经存在该对象,直接返回.

s2 指向的就是字符串常量池中 “1” 的首地址.

而 == 判断的是地址是否相同.显然不同.

s3 与 s4 不同之处在于,执行 s3.intern() 时,字符串常量池中还没有字符串对象,所以需要在字符串常量池中创建一个对象.其余的与上相同.

  • jdk 7

s 与 s2 情况与 jdk6 一样.

但是执行 s3.intern() 时,字符串常量池中还没有字符串对象,此时先堆中有没有该字符串对象,如果有,就把堆中字符串对象的引用保存到字符串常量池中.

所以 s4 指向的和 s3 一样,都是堆中 String 对象的引用.

所以判断为 true.

总结 :

jdk6 : 当调用 intern 方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用.否则,将该字符串对象添加到字符串常量池中,并且返回该字符串对象的引用.

jdk6+ : 当调用 intern 方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用.否则,如果该字符串对象已经存在于 Java堆 中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用.

jdk6 字符串只存在于 字符串常量池.

jdk6+ 字符串存在于字符串常量池和 Java堆.

那么回到上面那个问题,字符串常量池中到底保存的是字符串对象,还是字符串对象的引用?

答案就是,jdk6 保存的是字符串对象,jdk6+ 既保存了字符串对象,又保存了字符串对象的引用.