在 Java 開發中,不可變類 指的是類的實例一旦創建完成,其成員變量的值就無法被修改。JDK 內建了許多不可變類,例如 Integer、Long 和 String 等。相對應地,可變類 則允許在創建實例後修改其成員變量值,我們日常編寫的大多數類都屬於可變類。
不可變對象天生具備線程安全特性,多線程環境下可直接共享,無需額外同步機制(如鎖),從而降低併發錯誤風險,減少同步開銷。
由於狀態固定,不可變類不需要考慮狀態變更帶來的副作用,代碼邏輯更清晰,單元測試也更容易編寫。
final 修飾符,防止被繼承。子類若覆寫父類方法並修改成員變量,會破壞不可變性。private final,確保引用不可變。但若成員是可變對象(如數組、集合),仍需進一步防護。public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone(); // 深拷貝
}
}String 類嚴格遵循上述五大原則:
final,不可繼承。value、偏移量 offset、長度 count 均為 private final。value 的 setter 方法。Arrays.copyOf 深拷貝。toCharArray() 返回數組拷貝而非原引用。public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private final int offset;
private final int count;
private int hash; // 默認為 0
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // 深拷貝
}
public char[] toCharArray() {
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
相同內容的字符串指向常量池同一內存地址,節省空間。若可變,修改一處會影響所有引用,違背常量池設計初衷。
多線程可安全共享同一字符串實例,無需同步。
類加載過程使用字符串作為類名,不可變性保證類名不被篡改,防止惡意代碼替換核心類(如將 java.sql.Connection 替換為 myhacked.Connection)。
字符串創建時即計算並緩存 hashCode,作為 HashMap 鍵時查找極快,這也是鍵常用字符串的原因。
若需頻繁修改字符串內容,會創建大量 String 對象,增加 GC 壓力。此時應使用 StringBuilder 或 StringBuffer。
雖然 value 為 final,但可通過反射修改其值:
String s = "Hello World";
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[5] = '_'; // 修改底層數組
System.out.println(s); // 輸出 "Hello_World"
這說明不可變性在語言層面有保障,但在運行時反射可繞過檢查,屬於特殊場景,不影響日常設計原則。
不可變類通過犧牲修改靈活性,換取線程安全、簡化設計、支援常量池與 Hash 緩存等顯著優勢。JDK 大量採用此模式(Integer、Double、String 等)。String 的不可變性特別服務於常量池複用、類加載安全、高效 Map 鍵操作等核心場景。合理使用不可變類能極大提升系統穩健性與性能。