String物件判等
千萬不要用 == 去判斷String物件是否相等,==比較的是地址。JVM只會共享字串常量,因此,即使是“看起來”值相同的字串,用==判斷也可能不相等。
舉例來說,下面這段程式碼中,變數x和y都指向了常量池中共享的"a",地址相同,但是z是Java堆中的新建物件的引用,其地址與x不同,所以返回了false。
並且每次new一個String物件時,即使字串內容相同,也會新開闢一片空間儲存物件,因此z和zCopy地址也是不用的。
這部分的細節原理在下一部分中解釋。總而言之,如果你只是想判斷兩個String物件的內容是否一樣,請使用x.equals(z)的形式。
程式碼一
String x = "a";String y = "a";String z = new String("a");String zCopy = new String("a");System.out.println(x==y);//trueSystem.out.println(x==z);//falseSystem.out.println(z==zCopy);//false
String與常量池
我們在給String型別的引用賦值的時候會先看常量池中是否存在這個字串物件的引用,若有就直接返回這個引用,若沒有,就在堆裡建立這個字串物件並在字串常量池中記錄下這個引用。
注意:常量池中存放的是引用,並不是例項!!!
下面結合具體程式碼來理解這段話,看下面這段程式碼
程式碼二
String x = "a";String y = "a" + "b";String z = "a";
用javap -v -c對.class檔案進行反編譯後,得到如下結果
可以看到,常量池中最中只保留了一份"a"的引用。因為在String z = "a";執行時,字串常量池中已經有"a"的引用了,不會重複建立。
同時我們注意到,對應String y = "a" + "b";這條語句,因為"a"和"b"都是編譯器就能確定的常量,所以常量池只保留了最終計算的結果,並沒有單獨保留"b"。
我們將程式碼稍作修改,然後再次反編譯。
程式碼三
String witcher = "Geralt";String sorceress = "Yennefer";String date = witcher + sorceress;
可以看出,最終常量池只儲存了"Geralt"和"Yennefer"兩個引用,而沒有存放拼接的結果。因為witcher和sorceress變數要執行時才能確定。但是如果將變數witcher和sorceress都宣告為final,那編譯期就可以確定,因此拼接結果的引用資訊也會放入常量池。
總結:
對於字串表示式而言
1、對於編譯期能直接確定的值(字面量、宣告為final的變數),會直接將表示式的結果放入常量池。
2、如果編譯期不能直接直接確定(非final的變數),那麼只將已經宣告字串字面常量放入常量池,表示式的結果不放入常量池。
另一個出鏡率很高的問題是如下的這段程式碼建立了幾個物件?
String s = new String("xyz");
關於這個問題網上眾說紛紜,這裡放上一種比較靠譜的說法。參考自R神的部落格請別再拿“String s = new String("xyz");建立了多少個String例項”來面試了吧
首先,換個問法,這段程式碼在執行時涉及幾個String例項?
一種合理的解釋是:兩個,一個是字串字面量"xyz"所對應的、駐留(intern)在一個全域性共享的字串常量池中的例項,另一個是透過new String(String)建立並初始化的、內容與"xyz"相同的例項。
StringBuilder與StringBuffer如果你檢視過原始碼,就會發現String物件是被final修飾的,這意味著它是不可變的。因此,當我們拼接字串時,會產生新的物件。為此,設計者們提供了StringBuilder類來避免產生過多的中間物件。當我們用+拼接字串時,編譯器會自動幫我們使用StringBuilder進行最佳化。
這次使用jad對程式碼二進行反編譯(直接用javap -v也可以,但是使用jad產生的結果更容易看懂)
得到如下結果 可以看到編譯器自動為我們使用了StringBuilder
String witcher = "Geralt";String sorceress = "Yennefer";String date = (new StringBuilder()).append(witcher).append(sorceress).toString();
有人會說,既然編譯器已經最佳化,我們就直接使用+拼接字串就可以啊,為什麼還要用StringBuilder?
來看這段程式碼
程式碼四
String witcher = "Geralt";String sorceress = "Yennefer";String res = "";for (int i = 0; i < 8; i++) { res += sorceress;}
對其反編譯,可得
String witcher = "Geralt";String sorceress = "Yennefer";String res = "";for(int i = 0; i < 8; i++) res = (new StringBuilder()).append(res).append(sorceress).toString();
可以看出,每一輪的for迴圈都新建了一個StringBuilder,這是完全沒有必要的。因此,我們應該在for迴圈外部先定義一個StringBuilder物件,這樣只新建了一個物件就完成了任務,效率大增。
StringBuffer和StringBuilder基本相同,但是它保證了執行緒安全,如果有多執行緒需求,可以按需使用。
String.intern()我們用下面這段程式碼來分析intern的作用
程式碼五
String witcher1 = new String("Geralt");String witcher2 = "Geralt";System.out.println(witcher1 == witcher2);//false System.out.println(witcher1.intern() == witcher2);//true
第三行顯然是false,這在本文最開始已經解釋過。
但是witcher1呼叫intern之後,地址就與witcher2相同了,這是為什麼?
原來,當一個物件呼叫intern方法時,會檢視常量池是否有與當前物件內容相同的字面量,如果有,就直接返回常量池中的引用資訊,如果沒有,就在常量池中補充當前物件的字面量,然後返回引用。
總結:
連結:https://juejin.cn/post/6907580432485711886