下面程序的運(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
對(duì)于 1 和 2 中兩個(gè)都是顯式創(chuàng)建的新對(duì)象非迹,使用 == 總是不等惩歉,String 的 equals 方法有被重寫(xiě)為值判斷,所以 equals 是相等的裁奇。
String str1 = "123";
System.out.println("123" == str1.substring(0)); //3,true
System.out.println("23" == str1.substring(1)); //4,false
對(duì)于 3 和 4 中 str1 的 substring 方法實(shí)現(xiàn)里面有個(gè) index == 0 的判斷桐猬,當(dāng) index 等于 0 就直接返回當(dāng)前對(duì)象,否則新 new 一個(gè) sub 的對(duì)象返回刽肠,而 == 又是地址比較溃肪,所以結(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)建了新字符串對(duì)象,可以簡(jiǎn)單看下源碼:
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
對(duì)于 11 來(lái)說(shuō)當(dāng)兩個(gè)字符串常量連接時(shí)(相加)得到的新字符串依然是字符串常量且保存在常量池中只有一份惫撰。
String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13); //12,false
對(duì)于 12 來(lái)說(shuō)當(dāng)字符串常量與 String 類(lèi)型變量連接時(shí)得到的新字符串不再保存在常量池中羔沙,而是在堆中新建一個(gè) String 對(duì)象來(lái)存放,很明顯常量池中要求的存放的是常量厨钻,有 String 類(lèi)型變量當(dāng)然不能存在常量池中了扼雏。
String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16); //13,true
對(duì)于 13 來(lái)說(shuō)此處是字符串常量與 String 類(lèi)型常量連接,得到的新字符串依然保存在常量池中夯膀,因?yàn)閷?duì) final 變量的訪問(wèn)在編譯期間都會(huì)直接被替代為真實(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
對(duì)于 14 來(lái)說(shuō) final String str18 = getBB() 其實(shí)與 final String str18 = new String(“b”) 是一樣的,也就是說(shuō) return “b” 會(huì)在堆中創(chuàng)建一個(gè) String 對(duì)象保存 ”b”棍郎,雖然 str18 被定義成了 final其障,但不代表是常量,因?yàn)殡m然將 str18 用 final 修飾了涂佃,但是由于其賦值是通過(guò)方法調(diào)用返回的励翼,那么它的值只能在運(yùn)行期間確定,因此指向的不是同一個(gè)對(duì)象辜荠,所以可見(jiàn)看見(jiàn)并非定義為 final 的就保存在常量池中汽抚,很明顯此處 str18 常量引用的 String 對(duì)象保存在堆中,因?yàn)?getBB() 得到的 String 已經(jīng)保存在堆中了伯病,final 的 String 引用并不會(huì)改變 String 已經(jīng)保存在堆中這個(gè)事實(shí)造烁;對(duì)于 str18 換成 final String str18 = new String("b"); 一樣會(huì)返回 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
對(duì)于 15 到 18 來(lái)說(shuō) str23 == str20 就是上面剛剛分析的惭蟋,而對(duì)于調(diào)用 intern 方法如果字符串常量池中已經(jīng)包含一個(gè)等于此 String 對(duì)象的字符串(用 equals(Object) 方法確定)則返回字符串常量池中的字符串,否則將此 String 對(duì)象添加到字符串常量池中药磺,并返回此 String 對(duì)象的引用告组,所以 str23.intern() == str20 實(shí)質(zhì)是常量比較返回 true,str23 == str20.intern() 中 str23 就是上面說(shuō)的堆中新對(duì)象癌佩,相當(dāng)于一個(gè)新對(duì)象和一個(gè)常量比較木缝,所以返回 false,str23.intern() == str20.intern() 就沒(méi)啥說(shuō)的了围辙,指定相等我碟。
注釋 11 到 14 深刻的說(shuō)明了我們?cè)诖a中使用 String 時(shí)應(yīng)該留意的優(yōu)化技巧!特別說(shuō)明 String 的 + 和 += 在編譯后實(shí)質(zhì)被自動(dòng)優(yōu)化為了 StringBuilder 和 append 調(diào)用姚建,其實(shí)在Java中是先構(gòu)建一個(gè)StringBuiler對(duì)象矫俺,然后使用append()方法拼接字符串最后調(diào)用toString()方法生成字符串。但是如果在循環(huán)等情況下調(diào)用 + 或者 += 就是在不停的 new StringBuilder 對(duì)象 append 了,這是及其浪費(fèi)的恳守,應(yīng)該直接創(chuàng)建StringBuilder來(lái)實(shí)現(xiàn)。
通過(guò)這些題說(shuō)明要想玩明白 Java String 對(duì)象的核心其實(shí)就是玩明白字符串的堆棧和常量池贩虾,虛擬機(jī)為每個(gè)被裝載的類(lèi)型維護(hù)一個(gè)常量池催烘,常量池就是該類(lèi)型所用常量的一個(gè)有序集合,包括直接常量(String缎罢、Integer 和 Floating Point 常量)和對(duì)其他類(lèi)型伊群、字段和方法的符號(hào)引用,池中的數(shù)據(jù)項(xiàng)就像數(shù)組一樣是通過(guò)索引訪問(wèn)的策精,由于常量池存儲(chǔ)了相應(yīng)類(lèi)型所用到的所有類(lèi)型舰始、字段和方法的符號(hào)引用,所以它在 Java 程序的動(dòng)態(tài)鏈接中起著核心的作用咽袜。