在一次面試中,被問(wèn)到這方面的問(wèn)題,搞得我一臉蒙逼,那時(shí)候還沒(méi)去了解JVM,為了以后不再出現(xiàn)這樣的情況,我就在這里好好梳理一下String創(chuàng)建會(huì)創(chuàng)建多少個(gè)對(duì)象呢?

先看代碼
public static void main(String[] args) {
String str1 = "abc";
String str2 = "a" + "b" + "c";
System.out.println("結(jié)果1:" + (str1 == str2));
String str3 = "a";
String str4 = str3 + "bc";
System.out.println("結(jié)果2:" + (str1 == str4));
System.out.println("結(jié)果3:" + (str1 == str4.intern()));
final String str5 = "a";
String str6 = str5 + "bc";
System.out.println("結(jié)果4:" + (str1 == str6));
}
代碼運(yùn)行結(jié)果:
結(jié)果1:true
結(jié)果2:false
結(jié)果3:true
結(jié)果4:true
上面的運(yùn)行結(jié)果需要從兩個(gè)方面來(lái)說(shuō)明:
一:java編譯器
二:java字符串常量池
java編譯器
我們來(lái)看看java編譯器將上面的源碼編譯成了什么樣子。(反編譯結(jié)果)
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println("結(jié)果1:" + (str1 == str2));
String str3 = "a";
String str4 = str3 + "bc";
System.out.println("結(jié)果2:" + (str1 == str4));
System.out.println("結(jié)果3:" + (str1 == str4.intern()));
String str5 = "a";
String str6 = "abc";
System.out.println("結(jié)果4:" + (str1 == str6));
}
源代碼和編譯后的代碼有很多不同點(diǎn)。str2和str6都變成了直接賦值為"abc",而str4卻沒(méi)有變化,這里也是java新手不太清楚的地方,其實(shí)由于常量字符串是在編譯的時(shí)候就是可以被確定的,又因"a","b"和"c"都是字符串常量,因此變量str2的值在編譯時(shí)就可以確定,同理因?yàn)閟tr5被final關(guān)鍵字修飾,也相當(dāng)于字符串常量,因此變量str6的值也在編譯時(shí)就可以確定,代碼編譯后就和String str="abc"一樣了。
也是因?yàn)樯厦娴脑騭tr4沒(méi)有改變,因?yàn)閟tr4并不能在編譯期確定。
java字符串常量池
在執(zhí)行代碼
String str1 = "abc"時(shí),JVM先去字符串常量池中查找,看是否已經(jīng)存在值為”abc”的對(duì)象,如果存在,則不再創(chuàng)建新的對(duì)象,而直接返回已存在對(duì)象的引用;反之,則先創(chuàng)建一個(gè)新的對(duì)象,然后加入到字符串池中,再將其引用返回。
String str1 = "abc"; // 會(huì)創(chuàng)建一個(gè)新的對(duì)象"abc"在字符串常量池中
String str2 = "abc"; // 因?yàn)樽址A砍刂幸呀?jīng)存在"abc",所以不會(huì)創(chuàng)建對(duì)象
這樣結(jié)果1為true,就可以理解了
String str3 = "a"; // 會(huì)創(chuàng)建一個(gè)新的對(duì)象"abc"在字符串常量池中
/*
下面這行代碼一共會(huì)創(chuàng)建三個(gè)對(duì)象:
1. 會(huì)在字符串常量池中創(chuàng)建一個(gè)"bc"對(duì)象
2. str3與 "bc" 進(jìn)行字符串連接時(shí),底層通過(guò)StringBuffer進(jìn)行連接,生成一個(gè)
StringBuffer對(duì)象(StringBuffer內(nèi)部字符不考慮)
3. 然后通過(guò)StringBuffer的append方法連接,通過(guò)toString()方法,將StringBuffer對(duì)象
轉(zhuǎn)為String,此時(shí)會(huì)產(chǎn)生一個(gè)新的堆內(nèi)存地址,str4指向這個(gè)新的內(nèi)存地址。
*/
String str4 = str3 + "bc";
// 因?yàn)閟tr4在堆內(nèi)存變量中,str1在字符串常量池中,所以==地址比較為false
System.out.println("結(jié)果2:" + (str1 == str4));
// str4.intern() 返回在字符串常量池中的"abc"引用,就和str1是同一對(duì)象,為true
System.out.println("結(jié)果3:" + (str1 == str4.intern()));
看完上面的代碼相信您已經(jīng)對(duì)字符串創(chuàng)建對(duì)象個(gè)數(shù)有了一定的了解,接下來(lái)我們?cè)賮?lái)看看另一種情況。
/*
下面的代碼,一共會(huì)創(chuàng)建兩個(gè)對(duì)象:
1. 在字符串常量池中創(chuàng)建"abc"對(duì)象
2. 在堆中創(chuàng)建的原實(shí)例字符串對(duì)象
*/
String str1 = new String("abc");
// str1只是java棧中的對(duì)象引用
注意:String類(lèi)型的引用是存在棧里。而字符串"abc"是存在字符串常量池中
// 在字符串常量池中創(chuàng)建一個(gè)"abc"對(duì)象
String str1 = "abc";
// 由于"abc"已經(jīng)在字符串常量池中已經(jīng)存在,所以不會(huì)再創(chuàng)建。
// 使用new關(guān)鍵字創(chuàng)建字符串,一定會(huì)在堆中創(chuàng)建一個(gè)實(shí)例,所以下面代碼會(huì)創(chuàng)建一個(gè)對(duì)象
String str2 = new String("abc");
// str1只是java棧中的對(duì)象引用
注意:String類(lèi)型的引用是存在棧里。而字符串"abc"是存在字符串常量池中
下面提升難度
public static void main(String[] args) {
String str1 = "a" + new String("b");
String str2 = "ab";
System.out.println(str1 == str2);
String str3 = str2 + "c";
str3.intern();
String str4 = "abc";
System.out.println(str3 == str4);
}
運(yùn)行結(jié)果:
false
true
注意:上面的運(yùn)行結(jié)果和jdk版本有關(guān),1.7和1.8都是上面的結(jié)果,1.7以下不是
上面的結(jié)果重點(diǎn)就是intern方法的作用,這里我只是拋磚引玉,想了解更多的讀者可以看看下面的這篇博客:
深入解析String#intern
筆者個(gè)人能力有限,如有錯(cuò)誤,請(qǐng)聯(lián)系我修改。