java.lang.String 是 Java 使用頻率最高的類,其**不可變性**是語言設計的基石,影響安全性、線程安全、性能優化、字符串池機制等核心特性。本文從源碼級原理、內存結構、常見誤區、最佳實踐四個維度,徹底講透 String 不可變性的來龍去脈。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final byte[] value; // JDK 9+ compact strings: byte[] + coder
// private final char[] value; // JDK 8-: char[]
private final byte coder; // 0 = LATIN1, 1 = UTF16
private int hash; // Default to 0
// 構造器:防禦性拷貝,確保內部數組不被外部修改
public String(char[] value) {
this.value = Arrays.copyOf(value, value.length);
}
// 所有「修改」方法實際返回新對象
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
byte[] val = value;
// ... 複製數組並修改 ...
return new String(val, coder);
}
return this;
}
// 連接操作底層調用 StringBuilder
public String concat(String str) {
int len = str.length();
if (len == 0) return this;
byte[] buf = Arrays.copyOf(value, value.length + len);
str.getBytes(buf, value.length, coder);
return new String(buf, coder);
}
}
final class:不可繼承,防止子類破壞不可變性final byte[] value:引用不可變,**數組內容同樣受保護**(無 setter、無暴露內部引用的方法)replace、substring、concat、toUpperCase...)均**返回新 String 對象**hash 緩存:首次計算後緩存,利用不可變性保證哈希碼穩定
String str = "LOVO JAVA";
str.replaceAll("JAVA", "J2EE"); // 返回新對象被丟棄!
System.out.println(str); // 輸出:LOVO JAVA ← 原對象未變
String str = "LOVO JAVA";
String newStr = str.replaceAll("JAVA", "J2EE");
System.out.println(newStr); // 輸出:LOVO J2EE
System.out.println(str); // 輸出:LOVO JAVA(原對象不變)
String result = " hello world "
.trim()
.toUpperCase()
.replace("WORLD", "JAVA")
.substring(0, 10);
| 價值 | 機制 | 實際收益 |
|---|---|---|
| 字符串常量池 | 字面量自動駐留、intern() 手動駐留 | 節省內存、== 比較可用於池內字符串 |
| 線程安全 | 不可變對象天然線程安全 | 無需同步、可自由共享、構建不可變對象圖 |
| HashMap/HashSet Key | 哈希碼不可變 → 永遠定位同一桶位 | 避免 Key 修改導致數據丟失、性能穩定 |
| 安全性 | 文件路徑、網絡地址、類加載名稱不可被篡改 | 防止注入攻擊、類加載器緩存鍵穩定 |
byte[] value + byte coder 替代 char[]
// 反模式:每次循環創建新 String + StringBuilder
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 相當於 new StringBuilder().append(s).append(i).toString()
}
// 最佳實踐:顯式使用 StringBuilder
StringBuilder sb = new StringBuilder(10000 * 4);
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
substring 共享底層 char[],持有長字符串引用導致無法 GCsubstring **複製數組**,徹底解決new String(longStr.substring(...)) 強制複製intern() 將字符串放入常量池(JDK 7+ 在堆,JDK 6 在永久區)intern() → 池膨脹 → OOM"text" 自動駐留StringBuilder(單線程)/ StringBuffer(多線程)equals(),**勿用 ==**final、無 setter、防禦性拷貝、不暴露可變組件CharSequence、StringJoiner、TextBlock (JDK 13+)A: 1) 字符串池複用節省內存 2) 線程安全無鎖 3) HashMap Key 安全 4) 類加載安全 5) hash 碼緩存 6) 防禦性拷貝簡化
A: String 不可變;StringBuilder 非線程安全、高性能;StringBuffer 同步、線程安全(已過時推薦 Builder)
A: 1-2 個。字面量 "abc" 在常量池(可能已存在),new 在堆創建新對象。若池無 "abc" 則創建 2 個。