在阅读 周志明《深入理解Java虚拟机时》,遇到了一个经典的案例,理解时花费了较多时间,这里分享一下自己的思考。
案例
public void test01() {
String str1=new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern()==str1);//true
String str2=new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern()==str2);//false
}
预备知识
在JDK 7以及以上的版本中,由于字符串常量池和String对象都处在 堆 上,因此字符串常量池储存的是对堆中字符串对象的引用。
intern()方法的作用:如果字符串是第一次出现,该方法会将字符串加入字符串常量池中;如果不是第一次出现,那么会将字符串在字符串常量池中的引用返回。
原因解释
那么为什么会出现上面的现象呢?
需要注意的是,字符串在Java中也是一类特殊的对象,只不过由于它常使用作了特殊的优化(如字符串常量池)。
在第一个案例中,由于"计算机软件"是第一次出现,所以intern()方法返回的就是str1的地址。
在第二个案例中,由于"java"先前出现过,因此intern()方法返回的是先前出现的"java"字符串地址(在sun.misc.Version中被加载)
图示如下:
部分思考
- 在阅读书本时,有过疑惑为什么要使用StringBuilder而不直接使用String?
- 为什么特意append?
public void test02() {
String str1=new StringBuilder("计算机软件").toString();
System.out.println(str1.intern()==str1);//false
String str2=new StringBuilder("java").toString();
System.out.println(str2.intern()==str2);//false
}
public void test03() {
String str1="计算机"+"软件";
System.out.println(str1.intern()==str1);//true
String str2="ja"+"va";
System.out.println(str2.intern()==str2);//true
}
可以看出,这里是运行结果和原案例中是不同的。
要理解这个问题,需要先知道
- 在任何地方出现的被""包裹的字符串都会被加入常量池
- JVM会将形如"ja"+"va"的字符串自动优化为"java"再加入字符串常量池
- StringBuilder在使用toString()方法时会产生新的字符串对象,不会从字符串常量池中获取,并且不会将生成的字符串加入字符串常量池 参考博客
1、使用StringBuider以及append()的原因:
而原案例test01()中,通过StringBuilder的append()方法巧妙地避开了JVM会将形如"ja"+"va"的字符串自动优化为"java"再加入字符串常量池的特性,因此可以在之后调用intern()方法将字符串加入常量池。以Str1为例,会将"计算机"和"软件"分别加入字符串常量池,而不会将"计算机软件"加入常量池。
2、test02()中都为false的原因:
由于特性在任何地方出现的字符串都会被加入常量池,在test02()中先创建了String对象"计算机软件",直接将"计算机软件"加入到字符串常量池中了,再将"计算机软件"转为StringBuild,最后再次通过toString()转为str1,这里的str1为新的字符串对象。而str1.intern()调用是返回的是"计算机软件"的地址,而不是str1的地址
3、test03()中都为true的原因:
直接使用String时,java虚拟机会自动优化,将"ja"和"va"自动合并成"java",并且由于在字符串常量池中存在,会直接返回字符串常量池中的引用。值得指出的是,这里的str1.intern()==str1的原因和原案例中是不同的,这里的str1在创建时就被加入到字符串常量池,因此str1.intern()在这里并不会执行将str1加入常量池的行为,而是直接返回字符串常量池中队str1的引用。
因此StringBuilder和append()方法缺一不可
由于笔者也刚接触JVM,写的不好望指正