Java筆試必考題(二)到底建立了幾個String對象

來源:互聯網
上載者:User

我們首先來看一段代碼:
Java代碼 :
String str=new String("abc");
緊接著這段代碼之後的往往是這個問題,那就是這行代碼究竟建立了幾個String對象呢?相
信大家對這道題並不陌生,答案也是眾所周知的,2個。接下來我們就從這道題展開,一起
回顧一下與建立String對象相關的一些JAVA知識。
我們可以把上面這行代碼分成String str、=、"abc"和new String()四部分來看待。String str只
是定義了一個名為str的String類型的變數,因此它並沒有建立對象;=是對變數str進行初
始化,將某個對象的引用(或者叫控制代碼)賦值給它,顯然也沒有建立對象;現在只剩下new
String("abc")了。那麼,new String("abc")為什麼又能被看成"abc"和new String()呢?我們來看
一下被我們調用了的String的構造器:
Java 代碼
public String(String original) {
//other code ...
}
大家都知道,我們常用的建立一個類的執行個體(對象)的方法有以下兩種:
1. 使用new建立對象。
2. 調用Class類的newInstance方法,利用反射機制建立對象。
我們正是使用new調用了String類的上面那個構造器方法建立了一個對象,並將它的引用賦
值給了str變數。同時我們注意到,被調用的構造器方法接受的參數也是一個String對象,
這個對象正是"abc"。由此我們又要引入另外一種建立String對象的方式的討論——引號內包
含文本。
這種方式是String特有的,並且它與new的方式存在很大區別。
Java代碼
String str="abc";
毫無疑問,這行代碼建立了一個String對象。
Java 代碼
String a="abc";
String b="abc";
那這裡呢?答案還是一個。
Java代碼
String a="ab"+"cd";
再看看這裡呢?答案仍是一個。有點奇怪嗎?說到這裡,我們就需要引入對字串池相關知
識的回顧了。
在JAVA虛擬機器(JVM)中存在著一個字串池,其中儲存著很多String對象,並且可以被
共用使用,因此它提高了效率。由於String類是final的,它的值一經建立就不可改變,因此
我們不用擔心String對象共用而帶來程式的混亂。字串池由String類維護,我們可以調用
intern()方法來訪問字串池。
我們再回頭看看String a="abc";,這行代碼被執行的時候,JAVA虛擬機器首先在字串池中查
找是否已經存在了值為"abc"的這麼一個對象,它的判斷依據是String 類equals(Object obj)方
法的傳回值。如果有,則不再建立新的對象,直接返回已存在對象的引用;如果沒有,則先
建立這個對象,然後把它加入到字串池中,再將它的引用返回。因此,我們不難理解前面
三個例子中頭兩個例子為什麼是這個答案了。
對於第三個例子:
Java 代碼
String a="ab"+"cd";
由於常量的值在編譯的時候就被確定了。在這裡,"ab"和"cd"都是常量,因此變數a的值在
編譯時間就可以確定。這行代碼編譯後的效果等同於:
Java代碼
String a="abcd";
因此這裡只建立了一個對象"abcd",並且它被儲存在字串池裡了。
現在問題又來了,是不是所有經過“+”串連後得到的字串都會被添加到字串池中呢?我
們都知道“==”可以用來比較兩個變數,它有以下兩種情況:
1. 如果比較的是兩個基本類型(char,byte,short,int,long,float,double,boolean),
則是判斷它們的值是否相等。
2. 如果表較的是兩個物件變數,則是判斷它們的引用是否指向同一個對象。
下面我們就用“==”來做幾個測試。為了便於說明,我們把指向字串池中已經存在的對象
也視為該對象被加入了字串池:
Java代碼
public class StringTest {
public static void main(String[] args) {
String a = "ab";// 建立了一個對象,並加入字串池中
System.out.println("String a = /"ab/";");
String b = "cd";// 建立了一個對象,並加入字串池中
System.out.println("String b = /"cd/";");
String c = "abcd";// 建立了一個對象,並加入字串池中
String d = "ab" + "cd";
// 如果d和c指向了同一個對象,則說明d也被加入了字串池
if (d == c) {
System.out.println("/"ab/"+/"cd/" 建立的對象 /"加入了/" 字串池中");
}
// 如果d和c沒有指向了同一個對象,則說明d沒有被加入字串池
else {
System.out.println("/"ab/"+/"cd/" 建立的對象 /"沒加入/" 字串池中");
}
String e = a + "cd";
// 如果e和c指向了同一個對象,則說明e也被加入了字串池
if (e == c) {
System.out.println(" a +/"cd/" 建立的對象 /"加入了/" 字串池中");
}
// 如果e和c沒有指向了同一個對象,則說明e沒有被加入字串池
else {
System.out.println(" a +/"cd/" 建立的對象 /"沒加入/" 字串池中");
}
String f = "ab" + b;
// 如果f和c指向了同一個對象,則說明f也被加入了字串池
if (f == c) {
System.out.println("/"ab/"+ b 建立的對象 /"加入了/" 字串池中");
}
// 如果f和c沒有指向了同一個對象,則說明f沒有被加入字串池
else {
System.out.println("/"ab/"+ b 建立的對象 /"沒加入/" 字串池中");
}
String g = a + b;
// 如果g和c指向了同一個對象,則說明g也被加入了字串池
if (g == c) {
System.out.println(" a + b 建立的對象 /"加入了/" 字串池中");
}
// 如果g和c沒有指向了同一個對象,則說明g沒有被加入字串池
else {
System.out.println(" a + b 建立的對象 /"沒加入/" 字串池中");
}
}
}
運行結果如下:
1. String a = "ab";
2. String b = "cd";
3. "ab"+"cd" 建立的對象 "加入了" 字串池中
4. a +"cd" 建立的對象 "沒加入" 字串池中
5. "ab"+ b 建立的對象 "沒加入" 字串池中
6. a + b 建立的對象 "沒加入" 字串池中
從上面的結果中我們不難看出,只有使用引號包含文本的方式建立的String對象之間使用“
+”串連產生的新對象才會被加入字串池中。對於所有包含new方式建立對象(包括null)
的“+”串連運算式,它所產生的新對象都不會被加入字串池中,對此我們不再贅述。
但是有一種情況需要引起我們的注意。請看下面的代碼:
Java代碼
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的值也是固定的,
它在類被編譯時間就已經確定了。也就是說:
Java 代碼
String s=A+B;
等同於:
Java 代碼
String s="ab"+"cd";
我對上面的例子稍加改變看看會出現什麼情況:
Java 代碼
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的值之前,他們何時
被賦值,以及被賦予什麼樣的值,都是個變數。因此A和B在被賦值之前,性質類似於一
個變數。那麼s就不能在編譯期被確定,而只能在運行時被建立了。
由於字串池中對象的共用能夠帶來效率的提高,因此我們提倡大家用引號包含文本的方式
來建立String對象,實際上這也是我們在編程中常採用的。
接下來我們再來看看intern()方法,它的定義如下:
Java 代碼
public native String intern();
這是一個本地方法。在調用這個方法時,JAVA虛擬機器首先檢查字串池中是否已經存在與
該對象值相等對象存在,如果有則返回字串池中對象的引用;如果沒有,則先在字串池
中建立一個相同值的String對象,然後再將它的引用返回。
我們來看這段代碼:
Java 代碼
public class StringInternTest {
public static void main(String[] args) {
// 使用char數組來初始化a,避免在a被建立之前字串池中已經存在了值
為"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)的關係。我們首先回顧一下堆和棧的區別:
• 棧(stack):主要儲存基本類型(或者叫內建類型)
(char、byte、short、int、long、float、double、boolean)和對象的引用,資料可以共用,
速度僅次於寄存器(register),快於堆。
• 堆(heap):用於儲存物件。
我們查看String類的源碼就會發現,它有一個value屬性,儲存著String對象的值,類型是
char[],這也正說明了字串就是字元的序列。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.