不可變類是並發編程、領域建模、函數式編程的基石。本文系統剖析不可變類設計六大原則、JDK String 源碼級實現、反射破壞不可變性實測、以及工程化應用指南。
private final,無 setter,構造器一次性初始化final class 防止子類破壞不變約束this 引用 (無啟動線程、無註冊回調)// 錯誤: 淺拷貝,外部修改數組影響內部狀態
public final class BadImmutable {
private final int[] data;
public BadImmutable(int[] data) { this.data = data; }
}
// 正確: 構造器深拷貝
public final class GoodImmutable {
private final int[] data;
public GoodImmutable(int[] data) {
this.data = data.clone(); // 或 Arrays.copyOf(data, data.length)
}
// Getter 也要防禦性拷貝
public int[] getData() { return data.clone(); }
}
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// JDK 9+ Compact Strings: byte[] + coder
private final byte[] value;
private final byte coder; // 0=LATIN1, 1=UTF16
private int hash; // 默認 0,懶加載緩存
// 構造器深拷貝
public String(char[] value) {
this.value = StringUTF16.compress(value); // 內部複製
this.coder = UTF16;
}
// 所有「修改」操作返回新對象
public String substring(int beginIndex, int endIndex) {
if (beginIndex == 0 && endIndex == value.length) return this;
return new String(value, beginIndex, endIndex, coder); // 共享底層數組 (JDK 7+)
}
// toCharArray 防禦性拷貝
public char[] toCharArray() {
char[] result = new char[length()];
getChars(result, 0);
return result;
}
}
public class ImmutableBreaker {
public static void main(String[] args) throws Exception {
String s = "Hello World";
System.out.println("修改前: " + s); // Hello World
// 1. 獲取私有 value 字段
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true); // 破壞封裝
// 2. 獲取底層 byte[] 引用
byte[] value = (byte[]) valueField.get(s);
// 3. 直接修改內存 (JDK 9+ byte[] + coder, 此處簡化演示)
// 實際需同時修改 coder 字段,此處僅演示原理
value[6] = '_'; // ' ' -> '_'
System.out.println("修改後: " + s); // Hello_World (不可變性被破壞!)
}
}
警告:反射破壞不可變性屬於非標準行為,生產環境嚴禁使用。JDK 9+ 模塊化系統 (
java.base不導出java.lang內部) 已大幅限制此類操作,需--add-opens才能繼續。
| 場景 | 可變類風險 | 不可變類優勢 |
|---|---|---|
| 並發訪問 | 需鎖/同步,死鎖風險 | 無鎖並發,性能最優 |
| Map Key / Set 元素 | 修改後導致查找失敗 | 哈希碼穩定,永不失效 |
| 緩存值 | 臟讀、一致性難保證 | 安全共享,無需拷貝 |
| 防禦性編程 | 到處 clone() 防禦性拷貝 | 直接傳遞引用,零拷貝 |
| 領域建模 | 實體狀態分散難追蹤 | 值對象語義清晰,DDD 最佳實踐 |
public record Person(String name, int age, LocalDate birthday) {
// 自動生成: final 字段、構造器、getter、equals/hashCode、toString
// 可添加緊湊構造器驗證
public Person {
if (age < 0) throw new IllegalArgumentException("年齡不能為負");
}
}
@Value
@Builder
public class Person {
String name;
int age;
LocalDate birthday;
}
public final class Person {
private final String name;
private final int age;
private Person(Builder b) { this.name = b.name; this.age = b.age; }
public static class Builder {
private String name;
private int age;
public Builder name(String n) { this.name = n; return this; }
public Builder age(int a) { this.age = a; return this; }
public Person build() { return new Person(this); }
}
}
不可變類 = final class + final fields + 深拷貝隔離 + 無 setter + 安全發布。String 作為典範,通過 常量池共享、懶加載 hash、Compact Strings 等優化,將不可變性發揮到極致。雖然反射可破壞,但屬於違規操作。現代開發首選 record 或 @Value,讓編譯器保證不可變契約,專注業務建模。