其他更多java基礎(chǔ)文章:
java基礎(chǔ)學(xué)習(xí)(目錄)
學(xué)習(xí)資料:
String類(lèi)API中文
深入解析String#intern
Java 中new String("字面量") 中 "字面量" 是何時(shí)進(jìn)入字符串常量池的?
new一個(gè)String對(duì)象的時(shí)候锌唾,如果常量池沒(méi)有相應(yīng)的字面量真的會(huì)去它那里創(chuàng)建一個(gè)嗎睛藻?我表示懷疑。
通過(guò)上一篇的學(xué)習(xí)娶眷,我們已經(jīng)了解了String源碼的方法,這一章年叮,我們就通過(guò)Stirng.intern()方法來(lái)延伸锉试,講一下String的其他方面。
字符串字面量
字符串字面量是在 Java?語(yǔ)言規(guī)范的3.10.5. String 字面量中定義的
關(guān)于字面量通俗點(diǎn)解釋就是揍移,使用雙引號(hào)""
創(chuàng)建的字符串次和,在堆中創(chuàng)建了對(duì)象后其引用插入到字符串常量池中(jdk1.7后),可以全局使用那伐,遇到相同內(nèi)容的字面量踏施,就不需要再次創(chuàng)建石蔗。舉個(gè)例子:
//這就是創(chuàng)建了一個(gè)aaa字符串字面量
String a = "aaa";
//簡(jiǎn)單來(lái)說(shuō),這就是創(chuàng)建了一個(gè)Stirng對(duì)象和一個(gè)aaa字符串字面量畅形,后面會(huì)詳細(xì)討論
String a = new String("aaa")
字符串常量池
java中常量池的概念主要有三個(gè):全局字符串常量池
养距,class文件常量池
,運(yùn)行時(shí)常量池
日熬。我們現(xiàn)在所說(shuō)的就是全局字符串常量池
棍厌,在下文中可能會(huì)簡(jiǎn)稱(chēng)常量池。對(duì)這個(gè)想弄明白的同學(xué)可以看這篇Java中幾種常量池的區(qū)分碍遍。
字符串常量池里面存的到底是對(duì)象定铜,還是引用呢?我查了很多資料怕敬,最后根據(jù)自己的測(cè)試和查到的各種說(shuō)法揣炕,認(rèn)為在jdk1.7后字符串常量池中存的是引用。在new一個(gè)String對(duì)象的時(shí)候东跪,如果常量池沒(méi)有相應(yīng)的字面量真的會(huì)去它那里創(chuàng)建一個(gè)嗎畸陡?我表示懷疑。問(wèn)題中虽填,R大的回答解答了我:
至于說(shuō):
之前一直有個(gè)結(jié)論就是:當(dāng)創(chuàng)建一個(gè)string對(duì)象的時(shí)候丁恭,去字符串常量池看是否有相應(yīng)的字面量,如果沒(méi)有就創(chuàng)建一個(gè)斋日。
這個(gè)說(shuō)法從來(lái)都不正確牲览。
對(duì)象在堆里。常量池存引用恶守。
這個(gè)字符串常量池的位置也是隨著jdk版本的不同而位置不同第献。在jdk6中,常量池的位置在永久代(方法區(qū))中兔港,此時(shí)常量池中存儲(chǔ)的是對(duì)象庸毫。在jdk7中,常量池的位置在堆中衫樊,此時(shí)飒赃,常量池存儲(chǔ)的就是引用了。在jdk8中科侈,永久代(方法區(qū))被元空間取代了载佳。這里就引出了一個(gè)很常見(jiàn)很經(jīng)典的問(wèn)題,看下面這段代碼臀栈。
@Test
public void test(){
String s = new String("2");
s.intern();
String s2 = "2";
System.out.println(s == s2);
String s3 = new String("3") + new String("3");
s3.intern();
String s4 = "33";
System.out.println(s3 == s4);
}
jdk6
false
false
jdk7
false
true
這段代碼在jdk6中輸出是false false
蔫慧,但是在jdk7中輸出的是false true
。我們通過(guò)圖來(lái)一行行解釋挂脑。
JDK1.6
String s = new String("2");
創(chuàng)建了兩個(gè)對(duì)象藕漱,一個(gè)在堆中的StringObject對(duì)象,一個(gè)是在常量池中的“2”對(duì)象崭闲。s.intern();
在常量池中尋找與s變量?jī)?nèi)容相同的對(duì)象肋联,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對(duì)象“2”,返回對(duì)象2的地址刁俭。String s2 = "2";
使用字面量創(chuàng)建橄仍,在常量池尋找是否有相同內(nèi)容的對(duì)象,發(fā)現(xiàn)有牍戚,返回對(duì)象"2"的地址侮繁。System.out.println(s == s2);
從上面可以分析出,s變量和s2變量地址指向的是不同的對(duì)象如孝,所以返回false
String s3 = new String("3") + new String("3");
創(chuàng)建了兩個(gè)對(duì)象宪哩,一個(gè)在堆中的StringObject對(duì)象,一個(gè)是在常量池中的“3”對(duì)象第晰。中間還有2個(gè)匿名的new String("3")我們不去討論它們锁孟。
s3.intern();
在常量池中尋找與s3變量?jī)?nèi)容相同的對(duì)象,沒(méi)有發(fā)現(xiàn)“33”對(duì)象茁瘦,在常量池中創(chuàng)建“33”對(duì)象品抽,返回“33”對(duì)象的地址。
String s4 = "33";
使用字面量創(chuàng)建甜熔,在常量池尋找是否有相同內(nèi)容的對(duì)象圆恤,發(fā)現(xiàn)有,返回對(duì)象"33"的地址腔稀。
System.out.println(s3 == s4);
從上面可以分析出盆昙,s3變量和s4變量地址指向的是不同的對(duì)象,所以返回false
JDK1.7
String s = new String("2");
創(chuàng)建了兩個(gè)對(duì)象烧颖,一個(gè)在堆中的StringObject對(duì)象弱左,一個(gè)是在堆中的“2”對(duì)象,并在常量池中保存“2”對(duì)象的引用地址炕淮。s.intern();
在常量池中尋找與s變量?jī)?nèi)容相同的對(duì)象拆火,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對(duì)象“2”,返回對(duì)象“2”的引用地址涂圆。String s2 = "2";
使用字面量創(chuàng)建们镜,在常量池尋找是否有相同內(nèi)容的對(duì)象,發(fā)現(xiàn)有润歉,返回對(duì)象“2”的引用地址模狭。System.out.println(s == s2);
從上面可以分析出,s變量和s2變量地址指向的是不同的對(duì)象踩衩,所以返回false
String s3 = new String("3") + new String("3");
創(chuàng)建了兩個(gè)對(duì)象嚼鹉,一個(gè)在堆中的StringObject對(duì)象贩汉,一個(gè)是在堆中的“3”對(duì)象,并在常量池中保存“3”對(duì)象的引用地址锚赤。中間還有2個(gè)匿名的new String("3")我們不去討論它們匹舞。
s3.intern();
在常量池中尋找與s3變量?jī)?nèi)容相同的對(duì)象,沒(méi)有發(fā)現(xiàn)“33”對(duì)象线脚,將s3對(duì)應(yīng)的StringObject對(duì)象的地址保存到常量池中赐稽,返回StringObject對(duì)象的地址。
String s4 = "33";
使用字面量創(chuàng)建浑侥,在常量池尋找是否有相同內(nèi)容的對(duì)象姊舵,發(fā)現(xiàn)有,返回其地址寓落,也就是StringObject對(duì)象的引用地址括丁。
System.out.println(s3 == s4);
從上面可以分析出,s3變量和s4變量地址指向的是相同的對(duì)象零如,所以返回true躏将。
再來(lái)一段變種代碼
通過(guò)上面的逐句分析,應(yīng)該都了解了為什么兩個(gè)版本的jdk返回值會(huì)不一樣了考蕾。那我們稍稍改變一下上面代碼中的語(yǔ)句順序祸憋,將intern方法與字面量賦值語(yǔ)句調(diào)換順序:
String s = new String("2");
String s2 = "2";
s.intern();
System.out.println(s == s2);
String s3 = new String("3") + new String("3");
String s4 = "33";
s3.intern();
System.out.println(s3 == s4);
答案是多少呢,大家可以稍微思考一下再往下看:
jdk6
false
false
jdk7
false
false
原理很簡(jiǎn)單肖卧,因?yàn)樵谡{(diào)用intern方法前蚯窥,先使用了字面量賦值語(yǔ)句,所以在常量池中都存在了與變量相同內(nèi)容的對(duì)象(jdk1.6)或?qū)ο蟮囊茫╦dk1.7+)塞帐,此時(shí)再調(diào)用intern方法拦赠,就會(huì)發(fā)現(xiàn)常量池里的對(duì)象地址和變量的地址不是指向同一個(gè)對(duì)象,自然就false了葵姥。對(duì)于這段不懂的同學(xué)可以評(píng)論荷鼠,我看需不需要再畫(huà)一次結(jié)構(gòu)圖和逐句解釋。
字面量是何時(shí)進(jìn)入常量池
通過(guò)上面兩段代碼榔幸,我們發(fā)現(xiàn)調(diào)用intern方法和字面量賦值的順序是很重要的允乐。我們將上面兩段代碼都通過(guò)javap命令查看其字節(jié)碼,發(fā)現(xiàn)在class類(lèi)常量池中都有“33”削咆。這說(shuō)明在運(yùn)行時(shí)牍疏,class常量池里的常量并不會(huì)直接全部加入到全局常量池中,那這是在什么時(shí)候加入的呢拨齐?我搜到了下面大神的回答
new String(“字面量”) 中 “字面量” 是何時(shí)進(jìn)入字符串常量池的?
簡(jiǎn)單來(lái)說(shuō):
HotSpot VM的實(shí)現(xiàn)來(lái)說(shuō)起惕,加載類(lèi)的時(shí)候荠割,那些字符串字面量會(huì)進(jìn)入到當(dāng)前類(lèi)的運(yùn)行時(shí)常量池坡贺,不會(huì)進(jìn)入全局的字符串常量池 ;
-
在字面量賦值的時(shí)候,會(huì)翻譯成字節(jié)碼ldc指令援岩,ldc指令觸發(fā)lazy resolution動(dòng)作
- 到當(dāng)前類(lèi)的運(yùn)行時(shí)常量池(runtime constant pool,HotSpot VM里是ConstantPool + ConstantPoolCache)去查找該index對(duì)應(yīng)的項(xiàng)
- 如果該項(xiàng)尚未resolve則resolve之掏导,并返回resolve后的內(nèi)容窄俏。
- 在遇到String類(lèi)型常量時(shí),resolve的過(guò)程如果發(fā)現(xiàn)StringTable已經(jīng)有了內(nèi)容匹配的java.lang.String的引用碘菜,則直接返回這個(gè)引用;
- 如果StringTable里尚未有內(nèi)容匹配的String實(shí)例的引用,則會(huì)在Java堆里創(chuàng)建一個(gè)對(duì)應(yīng)內(nèi)容的String對(duì)象限寞,然后在StringTable記錄下這個(gè)引用忍啸,并返回這個(gè)引用出去。
String“+”符號(hào)的實(shí)現(xiàn)
在我們使用中經(jīng)常會(huì)用到+符號(hào)來(lái)拼接字符串履植,但是這個(gè)+符號(hào)在String中的實(shí)現(xiàn)還是有講究的计雌。如果是相加含有String對(duì)象,則底部是使用StringBuilder實(shí)現(xiàn)的拼接的
String str1 ="str1";
String str2 ="str2";
String str3 = str1 + str2;
如果相加的參數(shù)只有字面量或者常量或基礎(chǔ)類(lèi)型變量玫霎,則會(huì)直接編譯為拼接后的字符串凿滤。
String str1 =1+"str2"+"str3";
這里有個(gè)小細(xì)節(jié)
如果使用字面量拼接的話庶近,java常量池里是不會(huì)保存拼接的參數(shù)的翁脆,而是直接編譯成拼接后的字符串保存,我們看看這段代碼:
String str1 = new String("aa"+"bb");
//String str3 = "aa";
String str2 = new StringBuilder("a").append("a").toString();
System.out.println(str2==str2.intern());
這段代碼的輸出是true
鼻种》捶可以得知,在str1變量的創(chuàng)建中叉钥,雖然我們用了字面量“aa”罢缸,但是我們常量池里并沒(méi)有aa,所以str2==str.intern()
才會(huì)返回true
投队。如果我們?nèi)サ魋tr3的注釋?zhuān)匦逻\(yùn)行枫疆,就會(huì)輸出false
。
個(gè)人疑問(wèn)
我在學(xué)習(xí)的過(guò)程中敷鸦,遇到了一個(gè)疑問(wèn)息楔,怎么都查不到是為什么,大家如果看到這里轧膘,可以順手寫(xiě)一下這段代碼钞螟,看是不是也會(huì)遇到這樣的問(wèn)題。
public static void main(String[] args){
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
@Test
public void test7(){
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
如上所示谎碍,分別在test環(huán)境和main方法里運(yùn)行相同代碼鳞滨,此時(shí)main函數(shù)里返回true
,test環(huán)境下卻是返回false
蟆淀。按邏輯這里應(yīng)該是返回true才對(duì)拯啦。但是我測(cè)試了將參數(shù)“1”改為“2“”或者“3”澡匪,兩者返回的都是true。