徹底理解 String.intern
然後可對 String.intern 有一個大體地認識。
本篇中的 Java 例子使用的是 jdk1.8。
Java 中的常量池分靜態常量池和執行時常量池。
靜態常量池
編譯後的 *.class 檔案中除了有類、方法等描述資訊以外,還有常量池,主要存放兩大類常量:字面量(Literal)和符合引用量(Symbolic References)。字面量相當於 Java 層面的常量的概念,如文字字串(數字)字面量,宣告為 final 型別的常量等。
符號引用屬於編譯原理中的概念,包含類、方法的資訊:
- 類和介面的全限定名
- 欄位名稱和描述符
- 方法名稱和描述符
執行時常量池
JVM 在完成類載入以後,將類、方法等資訊儲存到元空間(jdk1.8 以前是方法區),會將 class 檔案中的常量池存到堆中(jdk1.7 以前是存在方法區的執行時常量池中)。
執行時常量池相對於 class 檔案常量池另外一個特性是具備動態性,並非預置入 class 檔案中常量池的內容才能進入堆中的執行時常量池,執行期間也可以將新的常量放入池中,比如使用 String 類的 intern() 方法。
Java 示例
code1:
public class StringInternDemo {
public static void main(String[] args) {
test1();
}
public static void test1() {
String b = new String("flower" );
String c = new StringBuilder().append("flow").append("er").toString();
System.out.println(c.intern() == c); // false
}
}
code2:
public class StringInternDemo0 {
public static void main(String[] args) {
test2();
}
public static void test2() {
String a = new String("flower" + "brown");
String c = new StringBuilder().append("flow").append("er").toString();
System.out.println(c.intern() == c); // true
}
}
為什麼 code1 輸出是 false,而 code2 是true 呢? code1 和 code2 的區別不大啊。前面說過 .class 檔案中有常量池,在完成類載入以後會將類中的常量池儲存到堆中,下面來看看 code1 和 code2 的位元組碼檔案中的常量池。
code1:
code2:
從 code1 和 code2 的 class 常量池中存在 flower 和 flowerbrown。
來看圖示:
code1:
在類載入完以後,堆中的常量池就存在 flower 了(網上很多文章說編譯期就確定了),String b = new String("flower");
會建立兩個物件,堆中建立一個,常量池中有一個(如果常量池中存在這個物件了,就不進行建立了)。明顯,c.intern 返回的物件的地址和 c 本身所指向的地址是不一樣的,輸出 false。
code2:
String a = new String("flower" + "brown");
會在類載入後在常量池中確定 flowerbrown,此時常量池中是沒有 flower 和 brown 的,所以這行程式碼 也只是建立兩個物件,String:字串常量池 這篇文章說會建立 4 個物件(flower、brown、flowerbrown、new String())其實是不對的。
c.intern() 發現常量池中並不存在 flower,則會在常量池中進行建立,但只是儲存 flower 在堆中的引用,這個引用和 c 都是指向相同的地址,所以這裡輸出是 true。