网上大神直接拿JVM实现代码来看的,我没这个水平,只好自己写点验证程序来观察并总结一下结果。
/*
* jdk1.8
*/
public class StrIntern {
private void print(String s1) {
System.out.print(s1);
}
public void testIntern1() {
String s1 = new StringBuilder("a").append("b").toString();
System.out.println(s1.intern()==s1); //true
String s2 = "ab";
System.out.println(s1.intern()==s1); //true
System.out.println(s2==s1); //true
}
public void testIntern2() {
String s1 = new StringBuilder("a").append("b").toString();
String s2 = "ab";
System.out.println(s1.intern()==s1); //false
System.out.println(s1.intern()==s2); //true
}
public void testIntern3() {
String s1 = new String("ab");
System.out.println(s1.intern()==s1); //false
}
public void testIntern4() {
String s1 = new StringBuilder("a").append("b").toString();
String s2 = new StringBuilder("a").append("b").toString();
System.out.println(s1.intern()==s1); //true
System.out.println(s2==s1); //false
System.out.println(s2.intern()==s1); //true
}
public static void main(String[] args) {
// TODO Auto-generated method stub
StrIntern obj = new StrIntern();
obj.testIntern4(); //观察结果
}
}
按照网上大神说的,jdk1.7以后的版本字符串池不在放在方法区的常量池中,而是移到了堆中,这一点没法验证;按照《深入理解Java虚拟机》(第二版)书中的描述,第一次调用intern时,如果在字符串池中找不到相同的字符串(利用String.equals()方法判断是否相同),则把这个字符串的引用地址放入字符串池。
也就是说,放入字符串池的实际不是字符串常量,而是一个引用地址,这个地址可能指向常量池中的字符串常量,也可能指向堆中的一个字符串对象。(网上大神用了StringTable来描述字符串池,可以简单将其理解成一个引用映射表)
testIntern1方法里面我们通过append方式组合了一个值为“ab”的字符串对象s1(后面我们就会看到为什么用这种方式),在intern之后,字符串池中保存的就是这个引用,所以实际上s1.intern()就是s1(了解JVM内存区域就可以了解到它们一个存在于StringTable里,一个存在于方法的栈帧Frame的局部变量表里)它们都指向同一个字符串对象,这个对象保存在堆里,而不是在常量池里。
而s2==s1,这个就很有趣了。事实上,这就跟
String s1 = "1";
String s2 = "1";
sysout(s1==s2); //true
一个意思,给s2赋值时,JVM一看字符串池里已经有一个符合条件的引用了,就直接把这个引用丢给了s2,导致了s2==s1。
testIntern2方法中,我们在intern之前有一条String s2 = "ab";语句,这就不对了。这条语句可以说隐含了一个intern()方法,他其实是先在字符串池里去找一个值equals“ab”的引用,但是并没有找到(因为我们之前new的那位兄弟在堆里呢,并没有在字符串池里保存引用,因为没intern)于是JVM在常量池里创建了一个值为“ab”的字符串常量,然后把这个常量的引用丢进了字符串池里。然后我们又调了s1.intern(),JVM到字符串池里一找,找到了这个指向常量的引用,它和s2指向同一个常量"ab"。
testIntern3方法中,由于我们在new String()的参数中使用了字符串常量"ab",所以相当于隐世地执行了一句“ab”.intern(),运行结果自然是false
testIntern4方法中,前面和1方法的前半一致不再赘述,但是s2是new出来的,他指向了一个存放在堆里的新对象,所以s1==s2自然是false。但是如之前所说,因为这两个字符串的值是相等的,s2.intern()的时候JVM一看:通过了equals方法嘛,直接就把字符串池里的引用返回了,而这个引用恰好就是跟s1指向地址相同的。
(补充:Oracle JDK中默认的虚拟机是HotSpot,它的引用reference实际实现就是个指针pointer。)