知識(shí)分享!String對(duì)象的詳解
前言
String是Java中十分常用的類,在面試題中也是出鏡率很高的???,本文將我自己學(xué)習(xí)中遇到的一些問(wèn)題進(jìn)行整理,如果有誤,歡迎指正。
String對(duì)象判等
千萬(wàn)不要用 ==
去判斷String對(duì)象是否相等,==
比較的是地址。JVM只會(huì)共享字符串常量,因此,即使是“看起來(lái)”值相同的字符串,用==
判斷也可能不相等。
舉例來(lái)說(shuō),下面這段代碼中,變量x和y都指向了常量池中共享的"a"
,地址相同,但是z
是Java堆中的新建對(duì)象的引用,其地址與x
不同,所以返回了false
。
并且每次new
一個(gè)String
對(duì)象時(shí),即使字符串內(nèi)容相同,也會(huì)新開辟一片空間存儲(chǔ)對(duì)象,因此z
和zCopy
地址也是不用的。
這部分的細(xì)節(jié)原理在下一部分中解釋。總而言之,如果你只是想判斷兩個(gè)String對(duì)象的內(nèi)容是否一樣,請(qǐng)使用x.equals(z)
的形式。
代碼一

String與常量池
我們?cè)诮oString類型的引用賦值的時(shí)候會(huì)先看常量池中是否存在這個(gè)字符串對(duì)象的引用,若有就直接返回這個(gè)引用,若沒(méi)有,就在堆里創(chuàng)建這個(gè)字符串對(duì)象并在字符串常量池中記錄下這個(gè)引用。
注意:常量池中存放的是引用,并不是實(shí)例!??!
下面結(jié)合具體代碼來(lái)理解這段話,看下面這段代碼
代碼二


可以看到,常量池中最中只保留了一份"a"
的引用。因?yàn)樵?code>String z = "a";執(zhí)行時(shí),字符串常量池中已經(jīng)有"a"
的引用了,不會(huì)重復(fù)創(chuàng)建。
同時(shí)我們注意到,對(duì)應(yīng)String y = "a" + "b";
這條語(yǔ)句,因?yàn)?#34;a"和"b"都是編譯器就能確定的常量,所以常量池只保留了最終計(jì)算的結(jié)果,并沒(méi)有單獨(dú)保留"b"
。
我們將代碼稍作修改,然后再次反編譯。
代碼三


可以看出,最終常量池只存儲(chǔ)了"Geralt"
和"Yennefer"
兩個(gè)引用,而沒(méi)有存放拼接的結(jié)果。因?yàn)?code>witcher和sorceress
變量要運(yùn)行時(shí)才能確定。但是如果將變量witcher
和sorceress
都聲明為final
,那編譯期就可以確定,因此拼接結(jié)果的引用信息也會(huì)放入常量池。
總結(jié)
對(duì)于字符串表達(dá)式而言
1、對(duì)于編譯期能直接確定的值(字面量、聲明為final的變量),會(huì)直接將表達(dá)式的結(jié)果放入常量池。
2、如果編譯期不能直接直接確定(非final的變量),那么只將已經(jīng)聲明字符串字面常量放入常量池,表達(dá)式的結(jié)果不放入常量池。
另一個(gè)出鏡率很高的問(wèn)題是如下的這段代碼創(chuàng)建了幾個(gè)對(duì)象?

首先,換個(gè)問(wèn)法,這段代碼在運(yùn)行時(shí)涉及幾個(gè)String實(shí)例?
一種合理的解釋是:兩個(gè),一個(gè)是字符串字面量"xyz"所對(duì)應(yīng)的、駐留(intern)在一個(gè)全局共享的字符串常量池中的實(shí)例,另一個(gè)是通過(guò)new String(String)創(chuàng)建并初始化的、內(nèi)容與"xyz"相同的實(shí)例。
StringBuilder與StringBuffer
如果你查看過(guò)源碼,就會(huì)發(fā)現(xiàn)String對(duì)象是被final
修飾的,這意味著它是不可變的。因此,當(dāng)我們拼接字符串時(shí),會(huì)產(chǎn)生新的對(duì)象。為此,設(shè)計(jì)者們提供了StringBuilder
類來(lái)避免產(chǎn)生過(guò)多的中間對(duì)象。當(dāng)我們用+
拼接字符串時(shí),編譯器會(huì)自動(dòng)幫我們使用StringBuilder進(jìn)行優(yōu)化。
這次使用jad對(duì)代碼二進(jìn)行反編譯(直接用javap -v
也可以,但是使用jad產(chǎn)生的結(jié)果更容易看懂)
得到如下結(jié)果 可以看到編譯器自動(dòng)為我們使用了StringBuilder

有人會(huì)說(shuō),既然編譯器已經(jīng)優(yōu)化,我們就直接使用+
拼接字符串就可以啊,為什么還要用StringBuilder
?
來(lái)看這段代碼
代碼四

可以看出,每一輪的for循環(huán)都新建了一個(gè)StringBuilder
,這是完全沒(méi)有必要的。因此,我們應(yīng)該在for循環(huán)外部先定義一個(gè)StringBuilder
對(duì)象,這樣只新建了一個(gè)對(duì)象就完成了任務(wù),效率大增。
StringBuffer
和StringBuilder
基本相同,但是它保證了線程安全,如果有多線程需求,可以按需使用。
String.intern()
我們用下面這段代碼來(lái)分析intern的作用
代碼五

原來(lái),當(dāng)一個(gè)對(duì)象調(diào)用intern
方法時(shí),會(huì)查看常量池是否有與當(dāng)前對(duì)象內(nèi)容相同的字面量,如果有,就直接返回常量池中的引用信息,如果沒(méi)有,就在常量池中補(bǔ)充當(dāng)前對(duì)象的字面量,然后返回引用。
總結(jié)
以上就是String
類型經(jīng)常引起疑惑的一些知識(shí)點(diǎn)。總結(jié)不易,如果有幫到你,希望可以點(diǎn)個(gè)贊,謝謝~
了解更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1L7411N77n
作者:EzioZhao
鏈接:https://juejin.cn/post/6907580432485711886
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。