Java String 3-字符串爲什麼不能修改

 String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。但是我們可以看到這樣的代碼:

String str = "hello" ;
str = str + " world" ;
str += "!!!";
System.out.println(str); // hello world!!!

 通過”+”操作,str字符串不就變成了”hello world”了嗎。

 實際上,我們不能這麼理解。在這段代碼中, str 原先指向一個 String 對象,內容是 “hello”,然後我們對 str 進行了”+” 操作,那麼 str 所指向的那個對象是否發生了改變呢?答案是沒有。這時, str 不指向原來那個對象了,而指向了另一個 String 對象,內容爲”Hello world”,原來那個”hello”對象還存在於內存之中,只是 str 這個引用變量不再指向它了,然後和上面的步驟一樣,str又指向新的對象”hello world!!!”,原先的”hello world”又被拋棄了。

分析

 通過上面的說明,我們很容易得出一個結論,如果經常對字符串進行各種各樣的修改,或者說,不可預見的修改,那麼使用 String 來代表字符串的話會引起很大的內存開銷。因爲 String 對象建立之後不能再改變,所以對於每一個不同的字符串,都需要一個 String 對象來表示,這樣會形成大量的垃圾空間。正是因爲String這個特點,所以在任何情況下,都不能出下下面的代碼:

String str = "hello";
for (int i = 0; i < 100; i++) {
    // 每次"+"操作都會使上一個str指向字符串成爲垃圾空間,而且"!"字符串也會成爲垃圾空間
    str += "!";
}
// 循環100此後,就算"!"字符串入池,也會產生101片垃圾空間

 這時,應該考慮使用 StringBuffer 類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。並且,這兩種類的對象轉換十分容易。

 同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都 new 一個 String。例如我們要在構造器中對一個名叫 s 的 String 引用變量進行初始化,把它設置爲初始值,應當這樣做:

private String s;
s = "Initial Value";

 而非:

s = new String("Initial Value");

 後者每次都會調用構造器,生成一個”Initial Value”的字符串,然後又new一片空間來轉載同樣的值,並且new出來的字符串對象不入池,性能低下且內存開銷大,並且沒有意義,因爲 String 對象不可改變,所以對於內容相同的字符串,只要一個 String 對象來表示就可以了。而第一種方式只會產生一個String的匿名對象,且會在共享池中尋找有無已經創建的相同內容的字符串,沒有才會創建併入池。

 上面的結論還基於這樣一個事實:對於字符串常量,如果內容相同, Java 認爲它們代表同一個 String 對象。而用關鍵字 new 調用構造器,總是會創建一個新的對象,無論內容是否相同。

 至於爲什麼要把 String 類設計成不可變類,是它的用途決定的。其實不只 String,很多 Java 標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優點,比如因爲它的對象是隻讀的,所以多線程併發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個對象來代表,可能會造成性能上的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章