好東西分享
我們首先來看一段代碼:
- String?str= new ?String( "abc" );??
String str=new String("abc");
緊接著這段代碼之后的往往是這個問題,那就是這行代碼究竟創(chuàng)建了幾個String對象呢?相信大家對這道題并不陌生,答案也是眾所周知的,2個。接下來我們就從這道題展開,一起回顧一下與創(chuàng)建String對象相關的一些JAVA知識。
我們可以把上面這行代碼分成String str、=、"abc"和new String()四部分來看待。String str只是定義了一個名為str的String類型的變量,因此它并沒有創(chuàng)建對象;=是對變量str進行初始化,將某個對象的引用(或者叫句柄)賦值給它,顯然也沒有創(chuàng)建對象;現(xiàn)在只剩下new String("abc")了。那么,new String("abc")為什么又能被看成"abc"和new String()呢?我們來看一下被我們調用了的String的構造器:
- public ?String(String?original)?{ ??
- ???? //other?code?... ??
- }??
public String(String original) { //other code ... }
大家都知道,我們常用的創(chuàng)建一個類的實例(對象)的方法有以下兩種:
- 使用new創(chuàng)建對象。
- 調用Class類的newInstance方法,利用反射機制創(chuàng)建對象。
我們正是使用new調用了String類的上面那個構造器方法創(chuàng)建了一個對象,并將它的引用賦值給了str變量。同時我們注意到,被調用的構造器方法接受的參數(shù)也是一個String對象,這個對象正是"abc"。由此我們又要引入另外一種創(chuàng)建String對象的方式的討論——引號內包含文本。
這種方式是String特有的,并且它與new的方式存在很大區(qū)別。
- String?str= "abc" ;??
String str="abc";
毫無疑問,這行代碼創(chuàng)建了一個String對象。
- String?a= "abc" ; ??
- String?b= "abc" ;??
String a="abc"; String b="abc";
那這里呢?答案還是一個。
- String?a= "ab" + "cd" ;??
String a="ab"+"cd";
再看看這里呢?答案仍是一個。有點奇怪嗎?說到這里,我們就需要引入對字符串池相關知識的回顧了。
在JAVA虛擬機(JVM)中存在著一個字符串池,其中保存著很多String對象,并且可以被共享使用,因此它提高了效率。由于String類是final的,它的值一經創(chuàng)建就不可改變,因此我們不用擔心String對象共享而帶來程序的混亂。字符串池由String類維護,我們可以調用intern()方法來訪問字符串池。
我們再回頭看看String a="abc";,這行代碼被執(zhí)行的時候,JAVA虛擬機首先在字符串池中查找是否已經存在了值為"abc"的這么一個對象,它的判斷依據(jù)是String類equals(Object obj)方法的返回值。如果有,則不再創(chuàng)建新的對象,直接返回已存在對象的引用;如果沒有,則先創(chuàng)建這個對象,然后把它加入到字符串池中,再將它的引用返回。因此,我們不難理解前面三個例子中頭兩個例子為什么是這個答案了。
對于第三個例子:
- String?a= "ab" + "cd" ;??
String a="ab"+"cd";
由于常量的值在編譯的時候就被確定了。在這里,"ab"和"cd"都是常量,因此變量a的值在編譯時就可以確定。這行代碼編譯后的效果等同于:
- String?a= "abcd" ;??
String a="abcd";
因此這里只創(chuàng)建了一個對象"abcd",并且它被保存在字符串池里了。
現(xiàn)在問題又來了,是不是所有經過“+”連接后得到的字符串都會被添加到字符串池中呢?我們都知道“==”可以用來比較兩個變量,它有以下兩種情況:
- 如果比較的是兩個基本類型(char,byte,short,int,long,float,double,boolean),則是判斷它們的值是否相等。
- 如果表較的是兩個對象變量,則是判斷它們的引用是否指向同一個對象。
下面我們就用“==”來做幾個測試。為了便于說明,我們把指向字符串池中已經存在的對象也視為該對象被加入了字符串池:
- public ? class ?StringTest?{ ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ????????String?a?=? "ab" ; //?創(chuàng)建了一個對象,并加入字符串池中 ??
- ????????System.out.println( "String?a?=?\"ab\";" ); ??
- ????????String?b?=? "cd" ; //?創(chuàng)建了一個對象,并加入字符串池中 ??
- ????????System.out.println( "String?b?=?\"cd\";" ); ??
- ????????String?c?=? "abcd" ; //?創(chuàng)建了一個對象,并加入字符串池中 ??
- ??
- ????????String?d?=? "ab" ?+? "cd" ; ??
- ???????? //?如果d和c指向了同一個對象,則說明d也被加入了字符串池 ??
- ???????? if ?(d?==?c)?{ ??
- ????????????System.out.println( "\"ab\"+\"cd\"?創(chuàng)建的對象?\"加入了\"?字符串池中" ); ??
- ????????} ??
- ???????? //?如果d和c沒有指向了同一個對象,則說明d沒有被加入字符串池 ??
- ???????? else ?{ ??
- ????????????System.out.println( "\"ab\"+\"cd\"?創(chuàng)建的對象?\"沒加入\"?字符串池中" ); ??
- ????????} ??
- ??
- ????????String?e?=?a?+? "cd" ; ??
- ???????? //?如果e和c指向了同一個對象,則說明e也被加入了字符串池 ??
- ???????? if ?(e?==?c)?{ ??
- ????????????System.out.println( "?a??+\"cd\"?創(chuàng)建的對象?\"加入了\"?字符串池中" ); ??
- ????????} ??
- ???????? //?如果e和c沒有指向了同一個對象,則說明e沒有被加入字符串池 ??
- ???????? else ?{ ??
- ????????????System.out.println( "?a??+\"cd\"?創(chuàng)建的對象?\"沒加入\"?字符串池中" ); ??
- ????????} ??
- ??
- ????????String?f?=? "ab" ?+?b; ??
- ???????? //?如果f和c指向了同一個對象,則說明f也被加入了字符串池 ??
- ???????? if ?(f?==?c)?{ ??
- ????????????System.out.println( "\"ab\"+?b???創(chuàng)建的對象?\"加入了\"?字符串池中" ); ??
- ????????} ??
- ???????? //?如果f和c沒有指向了同一個對象,則說明f沒有被加入字符串池 ??
- ???????? else ?{ ??
- ????????????System.out.println( "\"ab\"+?b???創(chuàng)建的對象?\"沒加入\"?字符串池中" ); ??
- ????????} ??
- ??
- ????????String?g?=?a?+?b; ??
- ???????? //?如果g和c指向了同一個對象,則說明g也被加入了字符串池 ??
- ???????? if ?(g?==?c)?{ ??
- ????????????System.out.println( "?a??+?b???創(chuàng)建的對象?\"加入了\"?字符串池中" ); ??
- ????????} ??
- ???????? //?如果g和c沒有指向了同一個對象,則說明g沒有被加入字符串池 ??
- ???????? else ?{ ??
- ????????????System.out.println( "?a??+?b???創(chuàng)建的對象?\"沒加入\"?字符串池中" ); ??
- ????????} ??
- ????} ??
- }??
public class StringTest { public static void main(String[] args) { String a = "ab";// 創(chuàng)建了一個對象,并加入字符串池中 System.out.println("String a = \"ab\";"); String b = "cd";// 創(chuàng)建了一個對象,并加入字符串池中 System.out.println("String b = \"cd\";"); String c = "abcd";// 創(chuàng)建了一個對象,并加入字符串池中 String d = "ab" + "cd"; // 如果d和c指向了同一個對象,則說明d也被加入了字符串池 if (d == c) { System.out.println("\"ab\"+\"cd\" 創(chuàng)建的對象 \"加入了\" 字符串池中"); } // 如果d和c沒有指向了同一個對象,則說明d沒有被加入字符串池 else { System.out.println("\"ab\"+\"cd\" 創(chuàng)建的對象 \"沒加入\" 字符串池中"); } String e = a + "cd"; // 如果e和c指向了同一個對象,則說明e也被加入了字符串池 if (e == c) { System.out.println(" a +\"cd\" 創(chuàng)建的對象 \"加入了\" 字符串池中"); } // 如果e和c沒有指向了同一個對象,則說明e沒有被加入字符串池 else { System.out.println(" a +\"cd\" 創(chuàng)建的對象 \"沒加入\" 字符串池中"); } String f = "ab" + b; // 如果f和c指向了同一個對象,則說明f也被加入了字符串池 if (f == c) { System.out.println("\"ab\"+ b 創(chuàng)建的對象 \"加入了\" 字符串池中"); } // 如果f和c沒有指向了同一個對象,則說明f沒有被加入字符串池 else { System.out.println("\"ab\"+ b 創(chuàng)建的對象 \"沒加入\" 字符串池中"); } String g = a + b; // 如果g和c指向了同一個對象,則說明g也被加入了字符串池 if (g == c) { System.out.println(" a + b 創(chuàng)建的對象 \"加入了\" 字符串池中"); } // 如果g和c沒有指向了同一個對象,則說明g沒有被加入字符串池 else { System.out.println(" a + b 創(chuàng)建的對象 \"沒加入\" 字符串池中"); } } }
運行結果如下:
- String a = "ab";
- String b = "cd";
- "ab"+"cd" 創(chuàng)建的對象 "加入了" 字符串池中
- a? +"cd" 創(chuàng)建的對象 "沒加入" 字符串池中
- "ab"+ b?? 創(chuàng)建的對象 "沒加入" 字符串池中
- a? + b?? 創(chuàng)建的對象 "沒加入" 字符串池中
從上面的結果中我們不難看出,只有使用引號包含文本的方式創(chuàng)建的String對象之間使用“+”連接產生的新對象才會被加入字符串池中。對于所有包含new方式新建對象(包括null)的“+”連接表達式,它所產生的新對象都不會被加入字符串池中,對此我們不再贅述。
但是有一種情況需要引起我們的注意。請看下面的代碼:
- public ? class ?StringStaticTest?{ ??
- ???? //?常量A ??
- ???? public ? static ? final ?String?A?=? "ab" ; ??
- ??
- ???? //?常量B ??
- ???? public ? static ? final ?String?B?=? "cd" ; ??
- ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ???????? //?將兩個常量用+連接對s進行初始化 ??
- ????????String?s?=?A?+?B; ??
- ????????String?t?=? "abcd" ; ??
- ???????? if ?(s?==?t)?{ ??
- ????????????System.out.println( "s等于t,它們是同一個對象" ); ??
- ????????}? else ?{ ??
- ????????????System.out.println( "s不等于t,它們不是同一個對象" ); ??
- ????????} ??
- ????} ??
- }??
public class StringStaticTest { // 常量A public static final String A = "ab"; // 常量B public static final String B = "cd"; public static void main(String[] args) { // 將兩個常量用+連接對s進行初始化 String s = A + B; String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } } }
這段代碼的運行結果如下:
- s等于t,它們是同一個對象
這又是為什么呢?原因是這樣的,對于常量來講,它的值是固定的,因此在編譯期就能被確定了,而變量的值只有到運行時才能被確定,因為這個變量可以被不同的方法調用,從而可能引起值的改變。在上面的例子中,A和B都是常量,值是固定的,因此s的值也是固定的,它在類被編譯時就已經確定了。也就是說:
- String?s=A+B;??
String s=A+B;
等同于:
- String?s= "ab" + "cd" ;??
String s="ab"+"cd";
我對上面的例子稍加改變看看會出現(xiàn)什么情況:
- public ? class ?StringStaticTest?{ ??
- ???? //?常量A ??
- ???? public ? static ? final ?String?A; ??
- ??
- ???? //?常量B ??
- ???? public ? static ? final ?String?B; ??
- ??
- ???? static ?{ ??
- ????????A?=? "ab" ; ??
- ????????B?=? "cd" ; ??
- ????} ??
- ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ???????? //?將兩個常量用+連接對s進行初始化 ??
- ????????String?s?=?A?+?B; ??
- ????????String?t?=? "abcd" ; ??
- ???????? if ?(s?==?t)?{ ??
- ????????????System.out.println( "s等于t,它們是同一個對象" ); ??
- ????????}? else ?{ ??
- ????????????System.out.println( "s不等于t,它們不是同一個對象" ); ??
- ????????} ??
- ????} ??
- }??
public class StringStaticTest { // 常量A public static final String A; // 常量B public static final String B; static { A = "ab"; B = "cd"; } public static void main(String[] args) { // 將兩個常量用+連接對s進行初始化 String s = A + B; String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } } }
它的運行結果是這樣:
- s不等于t,它們不是同一個對象
只是做了一點改動,結果就和剛剛的例子恰好相反。我們再來分析一下。A和B雖然被定義為常量(只能被賦值一次),但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什么樣的值,都是個變數(shù)。因此A和B在被賦值之前,性質類似于一個變量。那么s就不能在編譯期被確定,而只能在運行時被創(chuàng)建了。
由于字符串池中對象的共享能夠帶來效率的提高,因此我們提倡大家用引號包含文本的方式來創(chuàng)建String對象,實際上這也是我們在編程中常采用的。
接下來我們再來看看intern()方法,它的定義如下:
- public ? native ?String?intern();??
public native String intern();
這是一個本地方法。在調用這個方法時,JAVA虛擬機首先檢查字符串池中是否已經存在與該對象值相等對象存在,如果有則返回字符串池中對象的引用;如果沒有,則先在字符串池中創(chuàng)建一個相同值的String對象,然后再將它的引用返回。
我們來看這段代碼:
- public ? class ?StringInternTest?{ ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ???????? //?使用char數(shù)組來初始化a,避免在a被創(chuàng)建之前字符串池中已經存在了值為"abcd"的對象 ??
- ????????String?a?=? new ?String( new ? char []?{? 'a' ,? 'b' ,? 'c' ,? 'd' ?}); ??
- ????????String?b?=?a.intern(); ??
- ???????? if ?(b?==?a)?{ ??
- ????????????System.out.println( "b被加入了字符串池中,沒有新建對象" ); ??
- ????????}? else ?{ ??
- ????????????System.out.println( "b沒被加入字符串池中,新建了對象" ); ??
- ????????} ??
- ????} ??
- }??
public class StringInternTest { public static void main(String[] args) { // 使用char數(shù)組來初始化a,避免在a被創(chuàng)建之前字符串池中已經存在了值為"abcd"的對象 String a = new String(new char[] { 'a', 'b', 'c', 'd' }); String b = a.intern(); if (b == a) { System.out.println("b被加入了字符串池中,沒有新建對象"); } else { System.out.println("b沒被加入字符串池中,新建了對象"); } } }
運行結果:
- b沒被加入字符串池中,新建了對象
如果String類的intern()方法在沒有找到相同值的對象時,是把當前對象加入字符串池中,然后返回它的引用的話,那么b和a指向的就是同一個對象;否則b指向的對象就是JAVA虛擬機在字符串池中新建的,只是它的值與a相同罷了。上面這段代碼的運行結果恰恰印證了這一點。
最后我們再來說說String對象在JAVA虛擬機(JVM)中的存儲,以及字符串池與堆(heap)和棧(stack)的關系。我們首先回顧一下堆和棧的區(qū)別:
- 棧(stack):主要保存基本類型(或者叫內置類型)(char、byte、short、int、long、float、double、boolean)和對象的引用,數(shù)據(jù)可以共享,速度僅次于寄存器(register),快于堆。
- 堆(heap):用于存儲對象。
我們查看String類的源碼就會發(fā)現(xiàn),它有一個value屬性,保存著String對象的值,類型是char[],這也正說明了字符串就是字符的序列。
當執(zhí)行String a="abc";時,JAVA虛擬機會在棧中創(chuàng)建三個char型的值'a'、'b'和'c',然后在堆中創(chuàng)建一個String對象,它的值(value)是剛才在棧中創(chuàng)建的三個char型值組成的數(shù)組{'a','b','c'},最后這個新創(chuàng)建的String對象會被添加到字符串池中。如果我們接著執(zhí)行String b=new String("abc");代碼,由于"abc"已經被創(chuàng)建并保存于字符串池中,因此JAVA虛擬機只會在堆中新創(chuàng)建一個String對象,但是它的值(value)是共享前一行代碼執(zhí)行時在棧中創(chuàng)建的三個char型值值'a'、'b'和'c'。
說到這里,我們對于篇首提出的String str=new String("abc")為什么是創(chuàng)建了兩個對象這個問題就已經相當明了了。
?
向原作者致敬,轉自:
作者:臧圩人(zangweiren)
網址:
http://zangweiren.iteye.com
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
