java String类的intern()方法

来源:互联网 发布:c语言经典案例 编辑:程序博客网 时间:2024/05/18 13:31

《深入理解java虚拟机》今天看到了2.2.6运行时常量池(当然其实之前寒假看过了,但是好久不看遗忘了,所以又从头开始看起...),谈到了String类的intern()方法,因为不是太了解,所以查了一下资料,运行了一下代码,学习了一下。参考资料网址:http://www.importnew.com/21024.html#comments


1、字符串两种赋值方式及区别:

1.1、通过字面量赋值

String str="str";


1.2、通过new来生成一个字符串对象

String str=new String("str");


1.3、二者在jvm中的区别

首先,应该明确,运行时常量池中仅仅存放2类内容:a各种字面量;b符号引用。

通过字面量赋值创建字符串时,会优先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串的引用;倘若不存在,则在常量池中生成一个该字符串,再将栈中的引用指向该字符串引用。

而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象(备注,JDK 7 以后,HotSpot 已将常量池从永久代转移到了堆中。所有此时调用String.intern()方法时,不会再复制实例,只是在常量池中记录首次出现的实例引用详细信息可参考《JDK8内存模型-消失的PermGen》一文),栈中的引用指向该对象。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加到常量池中,并返回指向该常量的引用。由StringBuilder创建的字符串在java堆上。


2、代码示例:

public class TempTest {    public static void main(String[] args) {        //test1        String str11="abc";        String str12=new String("abc");        String str13=str12.intern();        System.out.println(str11==str12);//false        System.out.println(str11==str13);//true        //test2        String baseStr="baseStr";        final String baseFinalStr="baseStr";        String str21="baseStr01";        String str22="baseStr"+"01";        String str23=baseStr+"01";        String str24=baseFinalStr+"01";        String str25=new String("baseStr01").intern();        System.out.println(str21==str22);//true        System.out.println(str21==str23);//false        System.out.println(str21==str24);//true        System.out.println(str21==str25);//true        //test3        String str31=new String("str")+new String("01");        str31.intern();        String str32="str01";        System.out.println(str31==str32);//true        //test4        String str41="str01";        String str42=new String("str")+new String("01");        str42.intern();        System.out.println(str41==str42);//false    }}

代码结果分析:

2.1、test1

str11:为字面量赋值,故为存放在常量池中。

str12:为new生成的对象值,故存放在堆中。

str13:str12通过intern方法将str12引用添加到常量池中,由于str12="abc",而常量池中已经有abc,所以在添加到常量池的过程中直接返回:常量池中abc的引用。故str11==str13。

所以打印结果为:true,false。


2.2、test2

str21:为字面量赋值,故为存放在常量池中。

str22:jdk1.6后,常量字符串的+操作,在编译期间直接合并为一个字符串。故也为字面量赋值,所以str22得到的是和str21相同的引用。

str23:实质是stringBuilder.append()生成的结果,故与str21不是同一引用。

str24:因为包含final字段,故在编译期间进行了常量替换,所以str24直接获得了常量池str21的引用。

str25:由intern()方法可知,str21和str25是一样的引用。

所以打印结果为:true,false,true,true。


2.3、test3

str31:new生成的String对象,所以指向堆中的引用。

str32:因为str32在进行字面量赋值前,因为intern()方法的调用,导致常量池中生成了一个值为“abc”,指向堆中的引用。

原理:JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。

所以打印结果为:true。


2.4、test4

str41:为常量池中引用

str42:为堆中引用

所以打印结果为:false



3、常见面试问题:

有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了:

Q:String s = new String(“xyz”),创建了几个String Object?

A:两个,常量池中的”xyz”和堆中对象。

Q:下列程序的输出结果:

String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1 == s2);

A:true,均指向常量池中对象。

Q:下列程序的输出结果:

String s1 = new String(“abc”);
String s2 = new String(“abc”);
System.out.println(s1 == s2);

A:false,两个引用指向堆中的不同对象。

Q:下列程序的输出结果:

String s1 = “abc”;
String s2 = “a”;
String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);

A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。

Q:下列程序的输出结果:

String s1 = “abc”;
final String s2 = “a”;
final String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);

A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。

Q:下列程序的输出结果:

String s = new String(“abc”);
String s1 = “abc”;
String s2 = new String(“abc”);

System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());

A:false,false,true,具体原因参考第二部分内容。