下面程序的運(yùn)行結(jié)果是什么?
String stra = "ABC";
String strb = new String("ABC");
System.out.println(stra == strb); //1,false
System.out.println(stra.equals(strb)); //2,true
對于 1 和 2 中兩個都是顯式創(chuàng)建的新對象,使用 == 總是不等,String 的 equals 方法有被重寫為值判斷,所以 equals 是相等的。
String str1 = "123";
System.out.println("123" == str1.substring(0)); //3,true
System.out.println("23" == str1.substring(1)); //4,false
對于 3 和 4 中 str1 的 substring 方法實(shí)現(xiàn)里面有個 index == 0 的判斷,當(dāng) index 等于 0 就直接返回當(dāng)前對象,否則新 new 一個 sub 的對象返回,而 == 又是地址比較,所以結(jié)果如注釋。源碼如下:
String str5 = "NPM";
String str6 = "npm".toUpperCase();
System.out.println(str5 == str6); //7,false
System.out.println(str5.equals(str6)); //8,true
toUpperCase 方法內(nèi)部創(chuàng)建了新字符串對象,可以簡單看下源碼:
public String toUpperCase(Locale var1) {
...
return new String(var15, 0, var3 + var4);
}
String str9 = "a1";
String str10 = "a" + 1;
System.out.println(str9 == str10); //11,true
對于 11 來說當(dāng)兩個字符串常量連接時(相加)得到的新字符串依然是字符串常量且保存在常量池中只有一份。
String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13); //12,false
對于 12 來說當(dāng)字符串常量與 String 類型變量連接時得到的新字符串不再保存在常量池中,而是在堆中新建一個 String 對象來存放,很明顯常量池中要求的存放的是常量,有 String 類型變量當(dāng)然不能存在常量池中了。
String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16); //13,true
對于 13 來說此處是字符串常量與 String 類型常量連接,得到的新字符串依然保存在常量池中,因為對 final 變量的訪問在編譯期間都會直接被替代為真實(shí)的值。
private static String getBB() {
return "b";
}
String str17 = "ab";
final String str18 = getBB();
String str19 = "a" + str18;
System.out.println(str17 == str19); //14,false
對于 14 來說 final String str18 = getBB() 其實(shí)與 final String str18 = new String(“b”) 是一樣的,也就是說 return “b” 會在堆中創(chuàng)建一個 String 對象保存 ”b”,雖然 str18 被定義成了 final,但不代表是常量,因為雖然將 str18 用 final 修飾了,但是由于其賦值是通過方法調(diào)用返回的,那么它的值只能在運(yùn)行期間確定,因此指向的不是同一個對象,所以可見看見并非定義為 final 的就保存在常量池中,很明顯此處 str18 常量引用的 String 對象保存在堆中,因為 getBB() 得到的 String 已經(jīng)保存在堆中了,final 的 String 引用并不會改變 String 已經(jīng)保存在堆中這個事實(shí);對于 str18 換成 final String str18 = new String("b"); 一樣會返回 false,原因同理。
String str20 = "ab";
String str21 = "a";
String str22 = "b";
String str23 = str21 + str22;
System.out.println(str23 == str20); //15,false
System.out.println(str23.intern() == str20); //16,true
System.out.println(str23 == str20.intern()); //17,false
System.out.println(str23.intern() == str20.intern()); //18,true
對于 15 到 18 來說 str23 == str20 就是上面剛剛分析的,而對于調(diào)用 intern 方法如果字符串常量池中已經(jīng)包含一個等于此 String 對象的字符串(用 equals(Object) 方法確定)則返回字符串常量池中的字符串,否則將此 String 對象添加到字符串常量池中,并返回此 String 對象的引用,所以 str23.intern() == str20 實(shí)質(zhì)是常量比較返回 true,str23 == str20.intern() 中 str23 就是上面說的堆中新對象,相當(dāng)于一個新對象和一個常量比較,所以返回 false,str23.intern() == str20.intern() 就沒啥說的了,指定相等。
注釋 11 到 14 深刻的說明了我們在代碼中使用 String 時應(yīng)該留意的優(yōu)化技巧!特別說明 String 的 + 和 += 在編譯后實(shí)質(zhì)被自動優(yōu)化為了 StringBuilder 和 append 調(diào)用,其實(shí)在Java中是先構(gòu)建一個StringBuiler對象,然后使用append()方法拼接字符串最后調(diào)用toString()方法生成字符串。但是如果在循環(huán)等情況下調(diào)用 + 或者 += 就是在不停的 new StringBuilder 對象 append 了,這是及其浪費(fèi)的,應(yīng)該直接創(chuàng)建StringBuilder來實(shí)現(xiàn)。
通過這些題說明要想玩明白 Java String 對象的核心其實(shí)就是玩明白字符串的堆棧和常量池,虛擬機(jī)為每個被裝載的類型維護(hù)一個常量池,常量池就是該類型所用常量的一個有序集合,包括直接常量(String、Integer 和 Floating Point 常量)和對其他類型、字段和方法的符號引用,池中的數(shù)據(jù)項就像數(shù)組一樣是通過索引訪問的,由于常量池存儲了相應(yīng)類型所用到的所有類型、字段和方法的符號引用,所以它在 Java 程序的動態(tài)鏈接中起著核心的作用。