觀察下列代碼:
//: Stringer.java
public class Stringer {
static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = new String("howdy");
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy (原物件未變)
}
} ///:~
q 傳入 upcase() 時實為參考副本,指向同一物理位置。方法內區域參考 s 隨方法結束消失,回傳的是新物件參考,原 q 完全不受影響。
const 對比呼叫 upcase(s) 時,我們通常不希望參數被修改——參數是提供資訊給閱讀者,非供修改。這項保證讓代碼更易讀寫。
C++ 需 const 關鍵字協助程式設計師承諾不修改原物件,但需處處記得加 const,易混淆難記憶。Java 透過 不可變物件設計 從語言層面解決此問題。
+ 覆寫與 StringBufferString 物件被設計為不可變。查閱文件可見:所有修改 String 的方法實際建立並回傳新 String,原物件原封未動。Java 無 const,但可自行實作不可變模式。
不可變物件完美解決別名問題:多重別名指向同一唯讀物件絕無副作用。
但對某些操作(如字串連接 +),產生新物件效率極低:
String s = "abc" + foo + "def" + Integer.toString(47);
若每次 + 都產生新 String,會產生大量中間物件頻繁觸發 GC。
編譯器自動建立 StringBuffer 計算上述運算式:
//: ImmutableStrings.java
public class ImmutableStrings {
public static void main(String[] args) {
String foo = "foo";
String s = "abc" + foo + "def" + Integer.toString(47);
System.out.println(s);
// 等價的 StringBuffer 手寫版:
StringBuffer sb = new StringBuffer("abc");
sb.append(foo);
sb.append("def");
sb.append(Integer.toString(47));
System.out.println(sb);
}
} ///:~
編譯器內部做法等同手寫 StringBuffer 版本——建立一個緩衝區,append() 直接加入字元,避免大量中間物件。雖仍會為字面常數建立 String 物件,但效率大幅提升。