今天看了大神的一篇講解intern()函數的文章,有所領悟,在此記錄。
一.背景知識
要理解這個函數,很多知識是必不可少的,下面一一列出。
intern()方法的作用:
JDK1.6:根據字符串對象,檢查常量池中是否存在相同字符串對象,如果不存在,在常量池中創建該字符串常量,返回該常量引用,否則直接返回已存在的常量引用。
JDK1.7:根據字符串對象,檢查常量池中是否存在相同字符串對象,如果不存在,
創建指向堆中字符串對象的引用,返回該引用,否則直接返回已存在的常量引用。java字符串字面量,即用雙引號括起來的字符串”a”,”b”等,屬於在編譯期就確定好的,因此會被放入常量池,並且在常量池中不會重複存在兩個相同的字符串。如果有兩個引用指向同一個字符串常量,那麼他們實際上指向位於常量池中的同一個對象。
所有引用變量都存在與java虛擬機棧中。他們或者指向堆中的一個對象,或者指向常量池中的一個對象。
- 在JDK1.7以前,運行時常量池存在於方法區,而在JDK1.7開始,運行時常量池被移動到了堆區。這使得,如果調用intern()時,發現常量池中並沒有該字符串常量,那麼不需要再創建一個常量,而是直接創建指向堆中字符串對象的引用!相當於重用了字符串對象。
- new String(“a”)必定會在堆中生成一個對象,不過可能是匿名的,同時也會在常量池中生成一個常量”a”(如果a還不在常量池中的話)。
- “a”+”b”並不會在常量池中生成常量”ab”,只會生成”a”和”b”,因爲字符串連接的結果是運行時才能確定的,編譯期無法確定。最終會返回一個存在於堆中的”ab”字符串對象。
- 對字符串使用==,判斷的是兩個引用指向的地址。需要注意的是,如果指向的是常量池中的對堆字符串對象的引用,那麼會使用堆字符串對象的地址進行判斷!
二.案例分析
這裏使用大神的案例
案例一
String str2 = "SEUCalvin";//新加的一行代碼,其餘不變
String str1 = new String("SEU")+ new String("Calvin");
System.out.println(str1.intern() == str1);
System.out.println(str1 == "SEUCalvin");
返回結果:false false
首先,在JDK1.7中,常量池會生成”SEUCalvin”對象,引用str2指向常量池中”SEUCalvin”,而引用str1指向堆中”SEUCalvin”對象,這兩個對象不同,雖然代表相同字符串,但是一個位於常量池,一個位於堆。另外在第二行代碼執行完畢後,”SEU”,”Calvin”也會出現在常量池中。
之後調用str1.intern(),此時常量池中存在相同字符串,那麼返回常量池對象的引用,顯然,常量池對象和堆對象地址不同,返回false。
最後,str1指向的是堆對象,字面量”SEUCalvin”指向的地址和str1.intern()相同,都是常量池中那個字符串的地址,返回false。
案例2
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
返回結果:true
首先堆中生成兩個匿名字符串對象,同時常量池中生成“1”字符串,最後,s3指向一個堆中的”11”的字符串對象。
第二行代碼執行時,由於常量池中並沒有”11”字符串,因此常量池中生成一個指向堆中”11”字符串對象的引用。(知識點4)
由於第三行代碼執行時,常量池中已存在字符串”11”的引用,所以s4也指向堆中的”11”對象。
s3和s4實際指向堆中的同一個對象,所以結果爲true。
額外實驗
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
返回結果:false
我看到有的資料說字符串字面量在類加載時被載入,那麼這樣的話似乎常量池中的字符串常量應該與相應代碼被執行的次序無關。但是這段代碼說明實際上並不是,執行次序很重要,如果
String s4 = "11";
先執行,就會在常量池生成一個字符串對象,如果
s3.intern();
先執行,那麼常量池就不會再生成一個對象,而是直接生成指向堆對象的引用。
此處略有疑惑,如有高手知曉,還望告知,十分感謝。