final修饰变量时锁住的是变量的“引用指向”而非对象内容;基本类型值固定,引用类型仅限指向初始对象;需配合不可变类才能实现真正不可变。

final修饰变量时到底锁住了什么
它锁住的是变量的“引用指向”,不是对象内容。比如 final List<string> list = new ArrayList();</string> 后,你不能执行 list = new ArrayList();(编译报错),但可以调用 list.add("a"); —— 因为 ArrayList 本身是可变的。
常见误判:以为加了 final 就等于“不可变对象”。其实只是防止重新赋值,和对象内部状态无关。
- 基本类型变量(如
int、boolean)加final后值彻底固定 - 引用类型变量加
final后只能指向初始化时的那个对象实例 - 若需真正不可变,得配合不可变类(如
ImmutableList)或手动封装
final方法为什么不能被重写但能被重载
final 方法禁止子类覆盖实现,但不影响同名不同参的重载。这是继承机制里的明确限制:JVM 在编译期就拒绝生成对 final 方法的 invokespecial 覆盖调用。
典型场景:模板方法模式中,把骨架逻辑用 final 封装,只开放钩子方法给子类实现。
立即学习“Java免费学习笔记(深入)”;
- 子类定义同签名方法会直接编译失败,错误信息类似:
Cannot override the final method from XXX - 子类定义同名但参数不同的方法是合法的,属于重载,与
final无关 - 注意反射绕过:虽然
setAccessible(true)可访问final字段,但无法通过反射“重写”final方法
final类在序列化和代理场景下的实际影响
final 类不能被继承,因此无法被 CGLIB 动态代理(CGLIB 靠生成子类实现),但不影响 JDK 动态代理(基于接口)和 Jackson/Gson 序列化(只要字段可读/可写)。
如果你用 Spring AOP 默认配置对 final 类加切面,会静默退化为 JDK 代理——前提是该类实现了接口;否则切面不生效,且无任何提示。
- Jackson 反序列化
final字段需要显式构造器或@JsonCreator,否则忽略该字段 - Gson 默认跳过
final字段(除非开启setExclusionStrategies或用@SerializedName配合构造器) - Lombok 的
@Data和@Value行为不同:@Value默认生成final字段 + 不可变构造器,@Data不加final
final字段的线程安全边界在哪
只有满足“正确构造”的 final 字段,才能保证对其他线程的可见性。所谓正确构造,是指构造过程中没有发生“this逃逸”——即构造函数结束前,this 引用未被发布(如未传给其他线程、未注册到静态容器、未启动内部线程等)。
一旦发生逃逸,即使字段是 final,其他线程仍可能看到默认值(如 null、0)。
- JMM 保证:线程 A 构造完对象后,线程 B 第一次读取该对象的
final字段,一定能看到构造器中写入的值(前提是没有逃逸) - 逃逸例子:
new Thread(() -> System.out.println(this)).start();放在构造函数里 - 不要依赖
final实现线程安全的“完全体”——它只解决发布时的可见性,不解决后续读写竞争










