目录

彻底搞懂String.intern()

小案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    private void test1() {
        String s1 = new String("a") + new String("b");
        String s2 = "ab";
        s1.intern();
        System.out.println(s1 == s2);
    }

    private void test2() {
        String s1 = new String("a") + new String("b");
        s1.intern();
        String s2 = "ab";
        System.out.println(s1 == s2);
    }

直接给出结果

jdk6: false;false

jdk7及以后: false;true

分析

定义:如果字符串常量池中存在值和调用者值相同则返回常量池中那个值的引用。如果不存在则将调用者的 object 放入字符串常量池并返回引用。

注意这个object,这是关键所在,但是英文定义没有展开。

jdk7之前如果不存在值与调用者值相同则将该值放入字符串常量池,jdk7及以后版本出于性能考虑将调用者的引用地址放入字符串常量池,这就是小案例中结果不同的根本原因。

也就是说字符串常量池在7以后不再只存放值,也有可能是引用,之后的操作都不会改变里面的情况

再来探讨一下字符串常量池中什么时候放入值或者引用,比如上题为什么要手动s1.intern(),new String("a") + new String("b")不会将ab放入常量池吗?

下面从字节码的角度分析test2过程:

https://gitee.com/lienhui68/picStore/raw/master/null/20200722144729.png

结论

  1. 只在常量池上创建对象

    https://gitee.com/lienhui68/picStore/raw/master/null/20200722140834.png

  2. 只在堆上创建对象

    https://gitee.com/lienhui68/picStore/raw/master/null/20200722141004.png

  3. 在堆上创建对象,在常量池上创建常量

    https://gitee.com/lienhui68/picStore/raw/master/null/20200722141128.png

    因为先对字面量a执行ldc,再new String, 可以查看字节码

    1
    2
    3
    4
    5
    6
    
     0 new #7 <java/lang/String>
     3 dup
     4 ldc #8 <a>
     6 invokespecial #9 <java/lang/String.<init>>
     9 pop
    10 return
    
  4. 在堆上创建对象,在常量池上创建引用

    https://gitee.com/lienhui68/picStore/raw/master/null/20200722141529.png

有了上面结论下面的案例就很好解决了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    private void t1() {
        String s1 = new String("a") + new String("b");
        String s2 = s1.intern();
        System.out.println(s1 == "ab");
        System.out.println(s2 == "ab");
    }

    private void t2() {
        new String("ab");
        t1();
    }

分别执行t1、t2

jdk版本 方法 结果 说明
6 t1 false;true 常量池中存放ab
7 t1 true;true 常量池中存放ab引用
7 t2 false;true 常量池中存放ab