String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String,若存在则,直接返回常量池中相应Strnig的引用;若不存在,则会复制该对象的引用,放入常量池中,然后返回这个String在常量池中的引用。因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,所以,这些等值的String对象通过intern()后使用==是可以匹配的。
public void test1() {
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println("s1 == s2:"+(s1 == s2));
}
运行截图:
该结果是在jdk8中运行的结果,jdk7及以上版本运行结果基本一致,只有jdk7之前的版本结果会不一致。这是因为在jdk7之前的版本,字符串常量池存储在方法区中,而到了jdk7以后,字符串常量池被移到了堆内存中,所以会有变化。
首先,在执行String s1 = new String("1") + new String("1")的时候,他会在堆中new一个StringBuilder的新对象sb,然后在new一个String的新对象str1,接着在字符串常量池中找有没有”1“这个字符串,没有的话就会在字符串常量池创建一个”1“的·字符串,然后把”1“传给String对象str1,然后执行sb.append(str1),同理,接着new一个String的新对象str2,接着执行sb.append(),执行完所有的append()后,调用sb.toString()方法,返回一个新String对象。所以,执行完这个过程后,在堆中就有”11“字符串的对象,在字符串常量池中有”1“字符串。
然后执行s1.intern();这个过程会在字符串常量池寻找有没有”11“这个字符串,如果存在,则直接将该引用返回给他,没有的话,会复制该对象的引用,放入常量池中,所以在字符串常量池中就有了一个指向”11“对象的引用。
接着执行 String s2 = "11"语句,他会在常量池中找有没有”11“的字符串,而第二条语句就有一个指向”11“对象的字符串,所以把这个引用赋值给s2。
所以,s1和s2指向的地址是一样的,因此s1 == s2 结果为true。
下面我们再来看一个例子,将上面 String s2 = "11"的语句放在语句 s1.intern()的前面,如下
public void test2() {
String s1 = new String("1") + new String("1");
String s2 = "11";
s1.intern();
System.out.println("s1 == s2:"+(s1 == s2));
}
运行截图:
由结果可以看出,对换位置后结果却是相反的,这是为什么呢?
首先,在执行String s1 = new String("1") + new String("1")的时候,他会在堆中new一个StringBuilder的新对象sb,然后在new一个String的新对象str1,接着在字符串常量池中找有没有”1“这个字符串,没有的话就会在字符串常量池创建一个”1“的·字符串,然后把”1“传给String对象str1,然后执行sb.append(str1),同理,接着new一个String的新对象str2,接着执行sb.append(),执行完所有的append()后,调用sb.toString()方法,返回一个新String对象。所以,执行完这个过程后,在堆中就有”11“字符串的对象,在字符串常量池中有”1“字符串。
接着执行String s2 = "11";这个过程会在字符串常量池找有没有相同的值,有则返回他的引用,没有的话就在字符串常量池创建。所以,执行完String s2 = "11"之后,字符串常量池就存在”11“这个字符串了,接着执行s1.intern();这个过程会在字符串常量池寻找有没有”11“这个字符串,显然,前一条语句执行后就有了,所以直接返回给他。
根据分析可以看出s1的字符串地址是指向堆中的,而s2是指向字符串常量池中的。所以s1 == s2 结果为false(注意,我们前面说到从jdk7以后,字符串常量池移到了堆中,所以我们这里所说的在堆中指的是不在字符串常量池的另一块区域)