final修饰基本类型变量时值不可再赋值,修饰引用类型时引用不可变但对象状态可能改变,修饰方法和类则限制重写与继承;final是实现不可变性的必要条件而非充分条件。

final修饰基本类型变量时,值不可再赋值
final 用在基本类型(如 int、boolean、double)上,表示该变量一旦初始化完成,就不能再被重新赋值。这不是“不可变对象”的概念,而是“不可再绑定”——它锁住的是变量与值之间的绑定关系。
常见错误现象:
- 编译时报错
cannot assign a value to final variable - 在构造器或初始化块中漏掉赋值,导致编译失败(未初始化的
final变量不合法)
使用场景:
- 常量定义(配合
static,如public static final int MAX_RETRY = 3;) - 构造器中一次性注入依赖(如
final UserService service;),避免后续被意外覆盖
注意点:
立即学习“Java免费学习笔记(深入)”;
-
final int x;必须在声明时、构造器内或实例初始化块中完成赋值 - 不同于 C++ 的 const,Java 的
final不影响底层内存或线程可见性保障(需配合volatile或同步机制才能确保安全发布)
final修饰引用类型变量时,引用不可变,但对象状态可能改变
这是最容易误解的一点:final List 中,list 这个引用不能再指向另一个 List 实例,但完全可以通过 list.add("foo") 修改其内部状态。
常见错误现象:
- 误以为加了
final就等于“不可变容器”,结果在多线程中出现竞态修改 - 把
final当作线程安全的替代方案,实际毫无作用
使用场景:
- 防止引用被意外重赋值(例如回调中捕获外部变量)
- 配合匿名内部类或 lambda 表达式使用(Java 8+ 允许“有效 final”,但语义一致)
关键区别:
-
final控制的是变量本身(栈上引用地址)是否可变 - 对象的可变性由其类设计决定(比如
String是不可变类,ArrayList不是) - 真正的不可变对象需要:所有字段为
final+ 类为final+ 不提供修改状态的方法 + 防止子类篡改(如防御性拷贝)
final修饰方法和类时,限制继承与重写
final 加在方法上(如 public final void close()),表示子类不能重写它;加在类上(如 final class StringUtils),表示该类不能被继承。
性能 / 兼容性影响:
- JVM 可对
final方法做内联优化(尤其在早期 HotSpot 中更明显,现代 JVM 已弱化此优势) -
final类天然杜绝了继承带来的多态不确定性,适合封装核心工具类或值对象
容易踩的坑:
- 过度使用
final类,导致无法 Mock(如单元测试中想 mock 一个final类的方法,需依赖 ByteBuddy 等字节码工具) - 在接口实现类中把
public方法声明为final,可能破坏框架约定(如 Spring AOP 代理要求方法可被重写)
参数差异:
-
final修饰形参(如void process(final String input))仅防止方法体内重新赋值,不影响调用方,也无运行时开销
不可变性 ≠ final,但 final 是构建不可变性的必要条件之一
很多人混淆“不可变性”(immutability)和 final。前者是语义契约,后者只是实现手段之一。
真正不可变对象的关键要素:
- 所有字段必须是
final(否则状态可能被修改) - 类自身必须是
final(否则子类可添加可变字段或重写方法) - 构造器必须完成完全初始化(防止 this 引用逃逸)
- 若持有可变对象(如
Date、ArrayList),必须做防御性拷贝(new ArrayList(input))并返回副本
容易被忽略的地方:
- 即使字段全
final,如果引用了非不可变类型且未做防御性拷贝,对象仍是可变的 -
final字段的初始化顺序很重要:若在构造器中调用子类可能重写的方法,仍可能导致读到未初始化的值(违反安全发布) - 序列化/反序列化可能绕过构造器逻辑,破坏不可变性(需自定义
readObject)









