許多開發者誤以為 final 修飾的引用類型變量意味著「物件不可變」,實際上 final 只保證引用地址不變,不限制物件內部狀態的修改。本文通過代碼實驗與記憶體模型分析徹底釐清此概念。
public class TestFinal {
public static void main(String[] args) {
final StringBuilder sb = new StringBuilder("123");
System.out.println("初始內容:" + sb + ",hashCode:" + sb.hashCode());
sb.append("我變了"); // 修改物件內容
System.out.println("修改後:" + sb + ",hashCode:" + sb.hashCode());
// 輸出顯示 hashCode 未變,證明引用未變
}
}
輸出結果:
初始內容:123,hashCode:14576877 修改後:123我變了,hashCode:14576877
| 數據類型 | final 效果 |
|---|---|
| 基本類型 (int, boolean 等) | 數值不可變,賦值後無法修改 |
| 引用類型 (StringBuilder, List 等) | 引用地址不可變,但物件內部狀態可自由修改 |
// 無法阻止方法內修改物件內容
public void method(final StringBuffer param) {
param.append("a"); // 編譯通過,運行生效
}
對策:若需不可變性,請使用不可變類別 (如 String、Guava ImmutableList) 或防禦性複製。
for (final int i = 0; i < 10; i++) { } // 編譯錯誤!基本類型 final 不可修改
for (final StringBuilder sb : list) { sb.append("x"); } // 編譯通過,可修改物件內容
final,減少可變狀態,提升代碼可讀性與線程安全性final List<String> list = Collections.unmodifiableList(new ArrayList<>());