String 作為 Java 中使用頻率最高的類,其不可變性是核心設計決策。本文從源碼實現、設計動機、JVM 執行優化三個維度,深度剖析 String 不可變的本質與工程智慧。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; // JDK 8 及之前
// private final byte[] value; // JDK 9+ Compact Strings
// private final byte coder; // JDK 9+ 編碼標識
}
final class:禁止繼承,杜絕子類破壞不可變性private final char[] value:引用不可變,內容訪問受控public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // 關鍵:拷貝而非直接引用
}
若直接賦值 this.value = value,外部持有相同陣列引用可修改內容,破壞不可變性。JDK 所有操作 value 的方法(substring、replace、concat 等)均採用建立新陣列/新物件模式,原物件絕不修改。
不可變物件天然線程安全,可自由共享。String 作為「地球上最常用的對象」,若需同步將造成災難性效能損耗。
不可變性使字串字面值可安全駐留常量池:
String s1 = "hello";
String s2 = "hello"; // 同一物件引用
String s3 = new String("hello"); // 堆上新物件
若可變,常量池機制將失效,記憶體佔用激增。
網路連結、檔案路徑、資料庫連線字串等敏感參數傳遞過程中,不可變性防範惡意篡改。
String 重寫 hashCode() 並快取結果:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
不可變保證 hash 值永不改變,可安全作為 HashMap/HashSet 的 Key。
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = indexOf(oldChar); // 關鍵:先尋找首個出現位置
if (i >= 0) {
char[] buf = new char[len];
for (int j = 0; j < i; j++) buf[j] = value[j]; // 複製前綴
while (i < len) {
char c = value[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this; // 無變化返回自身
}
為何不直接遍歷替換? 為了優化「無實際變更」場景:若字串不包含 oldChar,直接返回 this,避免新建物件,支援常量池複用策略。
// avoid getfield opcode char[] val = value; // 將堆上陣列引用複製到局部變量表
JVM 記憶體模型背景:
value 陣列)指令層面差異:
| 訪問方式 | 指令序列 | 開銷 |
|---|---|---|
直接訪問 value | aload_0 → getfield value → ... | 每次迴圈需從堆讀取到棧幀再入操作數棧 |
局部變量 val | aload_0 → getfield value → astore_1 → aload_1 ... | 首次 getfield 後,後續迴圈僅在棧幀內部轉移(局部變量表 ↔ 操作數棧) |
對於 replace 這類高頻、大迴圈方法,將陣列引用快取到局部變量,消除了迴圈內重複的堆→棧數據搬運,體現 JDK 開發者對 JVM 執行特性的極致理解。
final 方法、無 setterfinal classfinal → private final char[] valueprivate → 封裝性value 陣列雖可變,但僅內部訪問且防禦性拷貝static {
// 具體內容因版本而異,通常涉及 Compact Strings、編碼器初始化等
}
JDK 9 引入 Compact Strings(byte[] + coder 替代 char[]),靜態塊常用於初始化編碼器、壓縮邏輯等。具體實現隨版本演進,核心目標均為記憶體效率與效能平衡。
String 的不可變性是語言設計、執行優化、記憶體管理、安全模型多重考量的結晶。從防禦性拷貝到 JVM 指令級優化,每一處細節都體現了「為最常用場景做極致優化」的工程哲學。理解這些機制,不僅能避免常見陷阱(如 += 迴圈拼接產生大量臨時物件),更能啟發我們在設計高頻核心類時的思維模式。