在Java中final关键字如何影响类方法与变量_Java不可变设计说明

final修饰变量仅保证引用不可变而非对象不可变;修饰方法禁止重写但允许重载;修饰类禁止继承且隐含所有方法final;final是不可变的必要非充分条件,需配合深拷贝和不可变视图。

final修饰变量:值不可变 ≠ 对象不可变

声明为 final 的变量只能赋值一次,但若其类型是引用类型(如 ArrayListStringBuilder),变量本身不可重新指向其他对象,而对象内部状态仍可修改。

  • final List list = new ArrayList(); → 合法:可执行 list.add("a")
  • list = new ArrayList(); → 编译错误:不能重新赋值
  • 若需真正不可变容器,应使用 Collections.unmodifiableList()List.of()(Java 9+)

final修饰方法:禁止子类重写,但不影响重载

final 方法在运行时不会被动态绑定覆盖,JVM 可能对其内联优化;它不阻止同名不同参的重载,也不影响静态分派逻辑。

  • 子类中定义 void foo(int x) 和父类 final void foo(String s) 是允许的
  • 子类尝试 @Override void foo(String s) 会触发编译错误:Cannot override the final method from Parent
  • 常见误用:在抽象类中把模板方法设为 final,却忘了把钩子方法(hook method)留为 protected 非 final

final修饰类:彻底关闭继承链

类被声明为 final 后,任何 extends 尝试都会在编译期报错;这也隐含意味着该类所有方法自动成为 final(即使没显式写),但字段仍可按需声明为 final 或非 final

  • final class StringUtils { ... }class MyUtils extends StringUtils 编译失败
  • 标准库中 StringIntegerLocalDateTime 均为 final 类,这是不可变性的基础保障
  • 注意:final 类仍可实现接口,也可被依赖注入框架代理(如 CGLIB 无法继承,但可用 JDK 动态代理接口)

不可变设计中 final 的真实作用边界

final 是不可变(immutability)的必要非充分条件。它只约束“引用不可变”或“结构不可扩展”,不保证深层状态安全。

  • 构造器中未防御性拷贝可变参数(如传入外部 new Date())→ 外部仍可修改该对象,破坏不可变性
  • 字段为 final 但类型是可变集合,且 getter 直接返回原始引用 → 调用方拿到后可随意修改
  • 正确做法:字段 final + 构造器深拷贝 + getter 返回不可变视图(如 Collections.unmodifiableXXX
public final class Config {
    private final Map props;

    public Config(Map input) {
        // 错误:props = input;
        this.props = Collections

.unmodifiableMap(new HashMap<>(input)); } public Map getProps() { return props; // 安全:不可修改副本 } }

很多人卡在“用了 final 就等于不可变”这层直觉上——其实 final 只锁住了引用和继承路径,真正的不可变需要整条构造与访问链都配合防御策略。