关于String.intern()和new StringBuilder("").append("").toString();

在《深刻理解Java虚拟机》书中,提到在jdk1.7的版本中用String.intern()返回引用。java

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

}

运行结果:true false
书中给的解释是:app

JDK 1.7(以及部分其余虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,所以intern()返回的引用和由StringBuilder建立的那个字符串实例是同一个。对str2比较返回false是由于“java”这个字符串在执StringBuilder.toString()以前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,所以返回true。

如今的疑问是“java”这个字符串在常量池中何时存在了?
我最开始的猜测是“java”这个字符串是否是常驻在常量池中的?那为何常驻在常量池中呢?Java虚拟机何时加载了“java”这个字符串?ide

最开始觉得是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类ui

/* 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);

这个问题解决了,而后我又发现了另一个问题。除了这些在虚拟机加载时就初始化的常量,定义其余的字符串常量,好比“nihao”.this

先运行这个代码
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.

我如今还没想通这个问题,StringBuilder的append方法没有改变字符串的引用地址,只是把其值改变了,为何加了append返回的是true,没有加append倒是false呢?若是在后面多加几个append返回的也是true。
也但愿有人能够解答一下这个问题code

相关文章
相关标签/搜索