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查看对象地址:
结果:
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观察;
可以发现s1.intern()的地址是常量s5的,这就相当于,String s5 = "redis";这样创建时直接把s5放入常量池中。
总结:记住上面作用的定义。
小插曲:
其实jdk本身也是自己自带了常量池字符串,可以看Version这个类:
有的jdk8还有保留Java这个字符串
深入jvm虚拟机一书中也提出了:
以下代码也可以验证,发现我们自己没有像上面”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:
很明显两个是不同的地址
看一下字节码:
发现创建了两个对象,会吧redis这个字符串放入常量池,显示intern的时候,常量池已经有了
比较append拼接:
String s33 = new StringBuilder("red").append("is").toString();
System.out.println(s33 == s33.intern());
debug:
显然是通过地址
查看字节码:
发现创建三个对象,但是并没有字符串“redis”,所以intern的时候,会吧redis放入常量池