解读String的intern()

intern()在面试中也是经常遇到的,本文从案例以及debug来主要讲解intern()。这一块也是困扰本人很久的一个问题,通过debug,查看字节码分析intern()的作用,希望对大家有所帮助。

首先记住intern()的作用:

如果SCP(字符串常量池)中存在与A内容一样的字符串对象C时,就返回C 的地址;

否则将A放入SCP中,返回A的地址。

案例1:

public class StringInternDemo {
    public static void main(String[] args) {
          String s1 = new StringBuilder("re").append("dis").toString(); // @479 堆对象
        String s4 = new StringBuilder("re").append("dis").toString(); // @481 堆对象
//        String s5 = "redis"; // @433,放回常量中,首次出现在常量池
//        System.out.println(s1 == s5);// 一个是常量一个是堆对象
        System.out.println(s1.intern()==s4.intern()); // 都想去常量池找,发现已经有了,直接返回@433
//        System.out.println(s1.intern() == s5);
        System.out.println(s1);
        System.out.println(s1.intern());
        System.out.println(s1 == s1.intern()); // s1是堆地址,s1.intern()去常量池找,发现已经有了s5,所以s1.intern()指向s5了,返回的是s5的地址。
                                                // 假如没有s5,常量池没有,就会吧s1放进常量池,返回s1的地址
    }
}

debug查看对象地址:

解读String的intern()_第1张图片

结果:

true
redis
redis
true

分析:

首先只有用双引号(“”)创建的的String才是常量,其他的方式都是新增一个堆对象,所以s1与s4式不同对象,看到堆地址也是不一样的;

s1.intern(),s4.intern(),按照上面intern()的作用都想去常量池找,都没有,s1.intern()先执行,所以会把s1放进常量池,地址为s1的的堆地址,当s4.intern()访问时,发现常量有了(s1放进去的)

所以直接返回s1.intern()存的地址,两个时相等的。

同样s1.intern() = s1的时候,常量池存放的地址就是s1存进去的,必然相等。

以上验证了可能看起来不太直观,那么我们继续新建一个字符串,也就是把上面注释的代码放开,直接放入常量池

         String s1 = new StringBuilder("re").append("dis").toString(); // @479 堆对象
        String s4 = new StringBuilder("re").append("dis").toString(); // @481 堆对象
        String s5 = "redis"; // @433,放回常量中,首次出现在常量池
        System.out.println(s1 == s5);// 一个是常量一个是堆对象
        System.out.println(s1.intern()==s4.intern()); // 都想去常量池找,发现已经有了,直接返回@433
        System.out.println(s1.intern() == s5);
        System.out.println(s1);
        System.out.println(s1.intern());
        System.out.println(s1 == s1.intern()); // s1是堆地址,s1.intern()去常量池找,发现已经有了s5,所以s1.intern()指向s5了,返回的是s5的地址。
                                                // 假如没有s5,常量池没有,就会吧s1放进常量池,返回s1的地址

结果:

false
true
true
redis
redis
false

debug观察;

解读String的intern()_第2张图片

可以发现s1.intern()的地址是常量s5的,这就相当于,String s5 = "redis";这样创建时直接把s5放入常量池中。

总结:记住上面作用的定义。

小插曲:

其实jdk本身也是自己自带了常量池字符串,可以看Version这个类:

有的jdk8还有保留Java这个字符串

解读String的intern()_第3张图片

深入jvm虚拟机一书中也提出了:

解读String的intern()_第4张图片

以下代码也可以验证,发现我们自己没有像上面”redis“新建一个字符串常量,但发现s2 == s2.intern()为false,所以在此之前常量池肯定是有的

        String s2 = new StringBuilder("open").append("jdk").toString();
        String s22 = new StringBuilder("open").append("jdk").toString();
        System.out.println(s2.intern()==s22.intern());
        System.out.println(s2);
        System.out.println(s2.intern());
        System.out.println(s2 == s2.intern());

------------------

注意:大家应该有发现以上比较的是利用append拼接的,接下来看一下没有用append拼接直接new String或者new StringBuilder

使用new String或者new StringBuilder都是至多创建两个对象

        String s3 = new StringBuilder("redis").toString();
        System.out.println(s3 == s3.intern());

结果是false

debug:

很明显两个是不同的地址

解读String的intern()_第5张图片

看一下字节码:

发现创建了两个对象,会吧redis这个字符串放入常量池,显示intern的时候,常量池已经有了

解读String的intern()_第6张图片

比较append拼接:

        String s33 = new StringBuilder("red").append("is").toString();
        System.out.println(s33 == s33.intern());

debug:

显然是通过地址

解读String的intern()_第7张图片

查看字节码:

发现创建三个对象,但是并没有字符串“redis”,所以intern的时候,会吧redis放入常量池

解读String的intern()_第8张图片

你可能感兴趣的:(java基础,java,intern,面试题目)