String.intern()初探

今天在看深入理解java虚拟机关于String.intern()返回引用测试时,看得我有点云里雾里

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);
        
    }

在JDK1.6中运行,会得到两个false。这个不难理解:

在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中(堆上的副本并不是堆对象的地址),返回的是常量中这个串实例的引用,而由StringBuilder创建的字符串实例在堆上

固然并不是一个引用,返回两个false

 

但在JDK1.7中,得到的答案则是一个true,一个false

这是因为,JDK1.7+中,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。

所以第一个true则很好解释。str1.intern是查询"计算机软件"这个串是否存在于常量池

很好显然是不存在,那么则将“计算机软件”这个串在堆上的引用(地址)放入到常量池中 并返回这个引用(地址)

所以为ture

那么第二个false应该如何解释呢?难道之前常量池中已经存放过java这个串的堆地址了吗

最后在网上的博客上找到了答案(最开始以为是StringBuilder的原因,查看了一下StringBuilder的源码,发现里面没有加载字符串常量,网上也找了关于intern()的,发现都只是对比JDK 1.6和JDK 1.7之间上面代码的运行结果比较,后来找了许久,终于找到一篇关于[String.intern()探究]: <http://baijiahao.baidu.com/s?id=1568390319555291&wfr=spider&for=pc>的文章,发现要去查看System的源码)

 

java虚拟机会自动调用System类

/* register the natives via the static initializer.
 *
 * VM will invoke the initializeSystemClass method to complete
 * the initialization for this class separated from clinit.
 * Note that to use properties set by the VM, see the constraints
 * described in the initializeSystemClass method.
 */
在System类中的注释可以知道,调用了initializeSystemClass方法,在此方法中调用了Version对象的init静态方法
sun.misc.Version.init();
因此sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化。
查看Version类定义的私有静态字符串常量如下:
private static final String launcher_name = "java";
private static final String java_version = "1.7.0_51";
private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
private static final String java_runtime_version = "1.7.0_51-b13";
在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以"java"被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。
String str2=new StringBuilder("1.7.0").append("_51").toString();
System.out.println(str2.intern()==str2);
View Code

 

那么还有一个疑问

先运行这个代码
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"这个字符串常量没有被预先加载到常量池中。

这两个又有上面区别呢?为什么答案不一样呢

第一个:常量池中实际上存的是“ni”和“hao”的堆内存引用

而str3的“nihao”则为.toString的堆内存 而未存放到常量池中,所以str.intern则是吧str3的堆内存放入常量池,故str3==str3.intern()

第二个:常量池在“nihao”的时候就把“nihao”的堆内存放入了常量池,而.toString又会为StringBuilder生成另一个副本命名为str3

str3(副本)的地址并不与str3.intern()第一行的“nihao”地址一样   固为false

str3

 

posted @ 2020-02-17 15:21  饼先生  阅读(65)  评论(0编辑  收藏