你真的瞭解String嗎?
不知道大家在做面試題時是否會遇到關於String的題,記得校招時,樓主經常遇到String的題,有時候會很懵逼。先來看一個例子:
public class StringTest { public static void main(String[] args) { String str1 = "hello"; String str2 = "hello"; String str3 = "he" + "ll" + "o"; System.out.println(str1 == str2); System.out.println(str1 == str3); String str = "o"; String str4 = "hell" + str; System.out.println(str1 == str4); String str5 = new String("hello"); System.out.println(str1 == str5); System.out.println(str4 == str5); System.out.println(str1.equals(str5)); String str6 = new String("beyond"); } } //輸出的結果 //true //true //false //false //false //true 複製程式碼
你答對了嗎?
二、細說原理
1:簡單說明==與equals()區別:
- ==:比較的是記憶體地址是否相同,即引用是否指向了記憶體中的同一個物件。指向同一個記憶體地址則為true;否則,為false。
- equals():比較的是兩個引用指向的值是否相同。如果指向的值是相同的,則返回true,否則,返回false。
2:從記憶體角度詳談String
首先我們要知道,String是Java的一個類,有兩種建立物件的方式【不考慮反射、clone、反序列化】。
- 方式一:String str1 = "hello"; 將物件放到了常量池中
- 方式二:String str2 = new String("hello"); 在堆中新建了一個物件
通過 方式一 建立String物件,會 先檢視常量池 中是否有這個字串常量,如果存在,則直接將引用指向這個常量(即例子中:str1==str2為true)。比較疑惑的會是str3,str4。因為str3由字串常量“he”、"ll"、“o”組成,編譯器發現這三個常量組成的字串常量已經存在了常量池中,即編譯期間就已經確定了最終值,則也會將引用指向“hello”字串常量。對於str4,因為編譯器不能在編譯時就確定字串最終的值,所以會將字串常量“hell”存放在常量池,再在堆中生成最終的物件【 引申一下 :通過“+”連線字串,底層是通過new StringBuilder()的append()方法進行拼接,所以應避免在迴圈中使用“+”來拼接字串,以免建立大量垃圾物件】。
通過 方式二 而建立String物件, 首先會先檢視常量池中是否存在這個字串常量 ,如果存在,則直接在對記憶體中new出新物件;否則,會先在常量池生成這個字串常量,再在堆中生成新物件(如圖中str6)。
3:使用javap命令從編譯結果驗證
javap是jdk自帶的反解析工具。它的作用就是根據class位元組碼檔案,反解析出當前類對應的code區(彙編指令)、本地變量表、異常表和程式碼行偏移量對映表、常量池等等資訊。
javap的用法格式: javap <options> <classes>
其中classes就是你要反編譯的class檔案。 在命令列中直接輸入javap或javap -help可以看到javap的options有如下選項:
-help--help-?輸出此用法訊息 -version版本資訊,其實是當前javap所在jdk的版本資訊,不是class在哪個jdk下生成的。 -v-verbose輸出附加資訊(包括行號、本地變量表,反彙編等詳細資訊) -l輸出行號和本地變量表 -public僅顯示公共類和成員 -protected顯示受保護的/公共類和成員 -package顯示程式包/受保護的/公共類 和成員 (預設) -p-private顯示所有類和成員 -c對程式碼進行反彙編 -s輸出內部型別簽名 -sysinfo顯示正在處理的類的系統資訊 (路徑, 大小, 日期, MD5 雜湊) -constants顯示靜態最終常量 -classpath <path>指定查詢使用者類檔案的位置 -bootclasspath <path>覆蓋引導類檔案的位置 複製程式碼
比如本例子:javap -verbose StringTest

三、總結
String在記憶體的存在形式有兩種,如果通過 直接賦值 的形式(方式一)會將物件直接 放到常量池 中【如果由幾個字串常量拼接而成,並且在編譯時就確定了最終的結果(即常量池中存在最終拼接而成的字串),則直接將引用指向這個字串常量;如果在編譯時不能確定最終的結果,則會將最終結果在堆中生成,並其中的字串常量會在常量池中存在】;如果通過 new的方式 (方式二), 先在常量池生成字串常量,再在堆中生成字串物件。