final仅锁定引用不可变,不保证对象内部状态不可变;需结合不可变容器、防御性拷贝、不可变视图及构造即冻结等措施实现深层防护。

加 final 不能防止对象内部被改,它只锁住“变量名指向谁”,不锁住“那个对象里有什么”。想防内部篡改,得看清引用是深还是浅——关键在对象本身是否可变,而不是你有没有写 final。
先分清:final 锁的是“浅层引用”,不是“深层状态”
声明 final List<string> items = new ArrayList();</string> 后:
- ✅ 不允许再写
items = new LinkedList();(引用地址被锁) - ✅ 不允许写
items = null; - ❌ 但完全允许
items.add("hacked");、items.clear();(ArrayList 内部数组仍可变) - ❌ 同样允许
((User)items.get(0)).setName("evil");(若 User 可变)
实战中判断“深浅”的三个信号
看到一个 final 引用,立刻问自己:
- 它指向的类型是不是 JDK 自带的不可变容器?比如
List.of()、Set.copyOf()、Map.of()—— 是,则深层安全 - 它指向的是自定义类?检查该类所有字段是否都为 private final,且没有提供 setter 或可变方法(如
add()、setX())—— 否则仍是浅层只读 - 它底层封装了可变对象(如
new StringBuilder()、new Date()、new ArrayList())?那 final 只是贴了个“勿动标签”,实际门没锁
真正防篡改的四步落地动作
别依赖 final 单打独斗,按顺序补位:
- 传入前做防御性拷贝:
new ArrayList(input)或List.copyOf(input)(后者要求 input 本身不可修改) - 对外暴露不可变视图:
Collections.unmodifiableList(items),调用 add/remove 直接抛UnsupportedOperationException - 构造即冻结:像库存项那样,把所有字段声明为 private final,只在构造器赋值,不提供任何修改入口
- 必要时用第三方不可变库:
ImmutableList(Guava)、Value(Immutables)或 Java 14+ 的record类,天然深不可变
特别注意两个高危陷阱
这些地方 final 看似安心,实则危险:
-
异步回调中 final + 可变集合:写
final List<string> log = new ArrayList();</string>,再在线程池里log.add(...)—— 多线程并发 add 会出ConcurrentModificationException,final 完全不管 -
构造函数 this 逸出 + final 字段:在构造器里就把
this发给监听器或线程,哪怕所有字段都是 final,其他线程也可能读到未初始化的默认值(如null、0)










