String 建立時寫入實體位址,不可變。任何修改操作都會產生新物件。為避免大量重複字串浪費記憶體,JVM 維護字串常量池(概念類似 ArrayList),相同字面值共用引用。
| 寫法 | 建立物件數 | 說明 |
|---|---|---|
String s = "abc"; | 1 (池中) | 查池無則建,回傳池引用 |
String s = new String("abc"); | 2 | 池中建 "abc" + 堆中建新物件,s 指向堆物件 |
s1 + s2 → JVM 建 StringBuilder、append()、toString() 產生新物件(堆)"a" + "b" → 編譯期常量折疊,直接產生 "ab" 置於常量池s + "c" → 視為變數連接,走 StringBuilder 流程==:比較記憶體位址(引用是否指向同一物件)equals():比較字串內容值String _abc = "abc";
String _2_abc = "abc";
String _def = "def";
String _abcdef = "abcdef";
String _new_abc = new String("abc");
String _new_abcdef = new String("abcdef");
String _abc_def = _abc + _def; // 變數連接 → 新物件
String _abc_add_def = "abc" + "def"; // 常量連接 → 池中 "abcdef"
String _new_abc_def = _new_abc + _def; // 變數連接 → 新物件
// 比較結果
_abc == _2_abc // true (同池引用)
_abc == _new_abc // false (池 vs 堆)
_abc == _new_abc.intern() // true (intern() 回傳池引用)
_abc_def == _new_abc_def // false (兩個獨立堆物件)
_abcdef == "abc" + "def" // true (常量折疊同池)
_abcdef == _abc_def // false (池 vs 堆)
_abcdef == _abc_def.intern() // true (intern 導向池)
_abcdef == _abc_add_def // true (常量連接同池)
_new_abcdef == _abc_add_def // false (堆 vs 池)
_new_abcdef == _new_abc_def // false (兩個獨立堆物件)
現代 JVM (1.5+) 自動將 s1 + s2 + s3 優化為 new StringBuilder().append(s1).append(s2).append(s3).toString()。
陷阱:迴圈中連接:
String s = "";
for (int i = 0; i < 10; i++) s += i + " "; // 每次迴圈產生新 StringBuilder!
正確寫法:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) sb.append(i).append(' ');
String s = sb.toString();
public static void setString(String s) { s = "321"; }
public static void setString(TestString ts) { ts.string = "321"; }
String str = "123";
setString(str); // str 仍為 "123" (傳值為引用副本,重新指向不影響原變數)
TestString ts = new TestString();
ts.string = "123";
setString(ts); // ts.string 變為 "321" (傳物件引用,修改欄位影響原物件)
equals(),比較引用用 ==StringBuilder (單執行緒) 或 StringBuffer (多執行緒)new String("literal")intern()