關於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

相關文章
相關標籤/搜索