final修饰引用类型变量本质是“引用不可变”,即变量指向的内存地址不可更改,但所指对象的内部状态仍可修改;需配合不可变类设计或Collections.unmodifiableXXX()等手段才能实现对象内容不可变。

Java 中的 final 引用类型变量,本质是“引用不可变”,不是“对象不可变”。这句话是理解的关键。
final 修饰引用类型:地址锁死,内容可变
当你写:
final List<String> names = new ArrayList<>();
你锁住的是 names 这个变量所指向的内存地址。之后不能再做:
立即学习“Java免费学习笔记(深入)”;
names = new LinkedList<>(); ❌ 编译报错 —— 地址变了
但你可以安全地做:
- names.add("Alice"); ✅
- names.set(0, "Bob"); ✅
- names.clear(); ✅
因为这些操作都没改变 names 变量本身存储的那个地址,只是在原对象内部增删改查。
常见误判场景
很多人以为加了 final 就等于“整个对象冻结了”,结果在多线程或工具类中误用,导致状态意外被修改。典型例子:
-
final StringBuilder sb = new StringBuilder("start"); →
sb.append("end")合法,字符串内容已变 -
final int[] arr = {1, 2}; →
arr[0] = 99;合法,数组元素可改 -
final Map<String, Integer> cache = new HashMap<>(); →
cache.put("key", 42);合法,map 内容可变
如何真正实现“对象不可变”?
要让对象内容也不可变,需组合使用:
- 引用用
final(防止重指向) - 对象自身设计为不可变(如用
ImmutableList.of()、Map.copyOf()、自定义类所有字段加final+ 无 setter + 防止外部获取可变内部引用) - 必要时配合
java.util.Collections.unmodifiableXXX()包装(运行期防护,非编译期)
例如:
final List<String> safeList = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("a", "b")));
此时再调用 safeList.add(...) 会抛 UnsupportedOperationException。
为什么 Java 这样设计?
因为 JVM 层面,final 的语义只作用于变量槽(variable slot)—— 它保证的是该槽里存的值(对引用类型就是地址值)不再变化。这是轻量、高效、编译期可验证的约束。而对象内部状态是否可变,属于业务逻辑层面,由类的设计者负责。


















