最近在读 《深入理解java虚拟机》,总结一下 String.intern 知识点,引入书中的一个题目:
public class RuntimeConstantPoolOOM {
public static void main(String[] args){
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}
这段代码在JDK 1.6中运行,会得到两个false,而在JDK1.7中运行,会得到一个true和一个false。产生差异的原因是:在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而StringBuildler创建的字符串实例在Java堆上,所以必然不是一个引用,将返回false。而JDK1.7中(以及部分其他虚拟机,例如JRockit)的intern()实现不会再 复制实例,只是在常量池中记录首次出现实例的引用,因此intern()返回的引用和由StringBuilder创建的字符串实例是同一个;对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用,不符合首次出现的规则,而"计算机软件"这个字符串则是首次出现的,因为返回true;
下面引入几幅图对 intern 方法作如下总结:
- new String 都是在堆上创建字符串对象。当调用 intern() 方法时,JDK1.6中编译器会将字符串添加到常量池中,并返回指向该常量的引用。
- 通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
- 常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
- 对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
final String str1 = ”ja” ;
final String str2 = ”va” ;
String str3 = str1+str2 ;
在编译时,直接替换成了 String str3 = ”ja” + ”va”,根据第三条规则,再次替换成 String str3=”JAVA” 。
- 常量字符串和变量拼接时(如:String str3 = baseStr + “01” ;)会调用 stringBuilder.append() 在堆上创建新的对象。
- JDK 1.7 后,intern 方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
另外参考 关于String.intern()和new StringBuilder("").append("").toString();
先运行这个代码 ①
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
通过上面的解释,运行结果为true.
在运行这个代码 ②
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
其结果是什么 ? 应该还是 true 吧 ,毕竟通过上一个运行结果可以知道 "nihao" 这个字符串常量没有被预先加载到常量池中 。
但是运行结果却是 false .
解释如下 :上面的 ① 代码等价于下面的代码
String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());
很容易分析出 :
“nihao” 最先创建在堆中 str3.intern() 然后缓存在字符串常连池中 运行结果为 true .
代码 ② 等价于
String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先创建在常量池中, 运行结果为false.
new String()和new StringBuilder()同样的原理,由此对于一道 经典的Java面试题
在Java中,new String("hello")这样的创建方式,到底创建了几个String对象 ?
题目下答案众说纷纭,有说1的有说2的,我觉得各有各的道理,如果常量池中有“hello”的字符串,当然只会创建1个,如果没有则会创建2个;
new String ("hello")相当于如下代码:
String temp = "hello"; // 在常量池中
String str = new String(temp); // 在堆上