String的#intern()方法新收获
作者:互联网
引入
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
题外话之运行时常量池、字符串常量池的关系及所在位置
-
在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代。
-
在JDK1.7字符串常量池和静态变量被从方法区拿到了堆中,运行时常量池剩下的还在方法区, 也就是hotspot中的永久代。
-
在JDK8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间(Metaspace)。方法区的实现,用的是本地内存(Native Memory)。
总结:目前 (jdk8及之后),字符串常量池不包含在运行时常量池中 (曾经逻辑包含),字符串常量池在堆中,运行时常量池在方法区中。
延伸
public static void main(String[] args) { String s = new String("aa"); String s1 = s.intern(); String s2 = "aa"; System.out.println(s == s2); //① System.out.println(s1 == s2); //② String s3 = new String("b") + new String("b"); String s4 = s3.intern(); String s5 = "bb"; System.out.println(s3 == s5 ); //③ System.out.println(s4 == s5); //④ }
上面这段代码的输出是?
第一反应
false true false true
①输出false,因为s是堆中对象的引用,s2是字符串常量池中对象的引用;②输出true,因为s1和s2是字符串常量池中同一对象的引用;③④跟①②相比,也没什么差别嘛。③输出false,因为s3是堆中对象的引用,s5是字符串常量池中对象的引用;④输出true,因为s4和s5是字符串常量池中同一对象的引用。(ps:s4和s5不是字符串常量池中对象的引用,后面会解释)
真是如此吗?
在jdk8下,程序运行后,结果出乎我的意料,输出结果竟然是
false true true // 出乎我意料的输出 true
看下String源码中#intern()方法的一段注释:
* When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned.
字面意思就是,当调用这个方法时,会去检查字符串常量池中是否已经存在这个字符串,若存在,就直接返回;若不存在,就把这个字符串常量加入到字符串常量池中,然后再返回其(已有)引用。
但是,其实在JDK1.6和 JDK1.7的处理方式是有一些不同的。
在JDK1.6中,如果字符串常量池中已经存在该字符串对象,则直接返回池中此字符串对象的引用。否则,将此字符串的对象添加到字符串常量池中,然后返回该字符串对象的引用 (备注:返回的是字符串常量池中对象的引用)。
在JDK1.7中,如果字符串常量池中已经存在该字符串对象,则返回池中此字符串对象的引用。否则,如果堆中已经有这个字符串对象了,则把此字符串对象的引用添加到字符串常量池中并返回该引用 (返回的是堆中对象的引用!);如果堆中没有此字符串对象,则先在堆中创建字符串对象,再返回其引用。(这也说明,此时字符串常量池中存储的是对象的引用,而对象本身存储于堆中)
于是代码中,String s = new String("aa");创建了两个“aa”对象,一个存在字符串常量池中,一个存在堆中。
String s1 = s.intern(); 由于字符串常量池中已经存在“aa”对象,于是直接返回其引用,故s1指向字符串常量池中的对象。
String s2 = "aa"; 此时字符串常量池中已经存在“aa”对象,所以也直接返回,故 s2和 s1的地址相同。②返回true。
System.out.println(s == s2); 由于s的引用指向的是堆中的“aa”对象,s2指向的是常量池中的对象。故不相等,①返回false。
重点来了
String s3 = new String("b") + new String("b"); 先说明一下,这种形式的字符串拼接,等同于使用StringBuilder的append方法把两个“b”拼接,然后调用toString方法,new出“bb”对象,因此“bb”对象是在堆中生成的。所以,这段代码最终生成了两个对象,一个是“b”对象存在于字符串常量池中,一个是 “bb”对象,存在于堆中,但是此时字符串常量池中是没有“bb”对象的。s3指向的是堆中的“bb”对象。
String s4 = s3.intern(); 调用了intern方法之后,在JDK1.6中,由于字符串常量池中没有“bb”对象,故创建一个“bb”对象,然后返回其引用。所以 s4 这个引用指向的是字符串常量池中新创建的“bb”对象。在JDK1.7中,则把堆中“bb”对象的引用添加到字符串常量池中,故s4和s3所指向的对象是同一个,都指向堆中的“bb”对象。
String s5 = "bb"; 在JDK1.6中,指向字符串常量池中的“bb”对象的引用,在JDK1.7中指向的是堆中“bb”对象的引用。
System.out.println(s3 == s5 ); 参照以上分析即可知道,在JDK1.6中③返回false(因为s3指向的是堆中的“bb”对象,s5指向的是字符串常量池中的“bb”对象),在JDK1.7中,③返回true(因为s3和s5指向的都是堆中的“bb”对象)。
System.out.println(s4 == s5); 在JDK1.6中,s4和s5指向的都是字符串常量池中创建的“bb”对象,在JDK1.7中,s4和s5指向的都是堆中的“bb”对象。故无论JDK版本如何,④都返回true。
综上,在JDK1.6中,返回的结果为:
false true false true
在JDK1.7中,返回结果为:
false true true true
因此,在jdk8下,我上面【第一反应】的理解是错的,s4和s5不是字符串常量池中对象的引用,而是堆中对象的引用。
参考链接
标签:常量,bb,对象,池中,intern,收获,字符串,String 来源: https://www.cnblogs.com/xm66/p/15707439.html