基础不牢,地动山摇

大家好,我们今天一起复习一下final 。

final 是 Java 里看起来简单,但很容易被误解的关键字。 很多人第一反应是 “final 等于加锁”“final 性能更高”。 这些说法不仅不准确,还会误导你在代码里乱用。

AI 生成代码时,也经常随手给方法或类加上 final。 你必须先搞清楚:它到底限制了什么。

我们先有一个概念,final 的核心不是“加锁”,而是三种“不能再改”的设计约束:

  • final 变量:证件只能绑定一次
  • final 方法:重写权限被盖章禁止
  • final 类:最终订单,不能再派生

锁管的是“同一时刻谁能进”,final 管的是“能不能再赋值、再覆写、再继承”。两者不是一回事。

一、final 变量:一旦赋值,不能再改

final 修饰变量,表示这个变量一旦完成赋值,就不能重新赋值。只能被赋值一次。

public class User {
    private final String id = "U1001";
}

这里要注意两点:

  • 引用类型的 final,限制的是 “变量指向谁”,不是 “对象内容不变”。
  • final 并不能让对象内部所有字段都自动变成不可变。
final List<String> list = new ArrayList<>();
list.add("Java"); // 可以修改对象内容
list = new ArrayList<>(); // 编译错误,不能改变引用

我们可以把 final 变量类比成: 写在证件上的身份编号,一旦确定,不能再改成另一个编号。 但证件里的其他信息,如果没有额外限制,仍然可能被修改。

所以:

  • final 基本类型:值不能改。
  • final 引用类型:引用不能改,但对象内容可能能改。
  • final 字段:必须在声明时、初始化块或构造器中赋值,且每个构造器都要保证赋值。
  • final 参数:方法内不能重新赋值,但不影响调用者,也不保证对象内容不变。
  • final 局部变量:常被 lambda / 匿名内部类捕获,Java 8 后“有效 final”也行。

一句话记忆:final 限制的是 “变量能不能再指向别的东西”。

二、final 方法:不能被子类重写

final 修饰方法,表示这个方法不能被子类重写。

public class BaseService {
    public final void init() {
        System.out.println("初始化");
    }
}

public class UserService extends BaseService {
    @Override
    public void init() { // 编译错误
    }
}

它的目的不是 “提高性能”,而是固定方法行为。 比如父类的初始化逻辑、校验逻辑、核心流程,不希望子类改变时,可以考虑 final。

类比理解: 父类已经规定了某件事的固定做法,子类只能沿用,不能改流程。

注意:

  • final 方法可以被继承和调用。
  • final 方法不能被重写,但可以被重载。
  • private 方法本来就不能被重写,加 final 通常没意义。
  • static 方法不能被重写,只能被隐藏;static final 可以禁止子类隐藏。
  • 构造器不能被重写,也不能加 final。

常见误用:
以为 final 方法不能被子类调用。错,子类照样能调用,只是不能改。

三、 final 类:不能被继承

final 修饰类,表示这个类不能被其他类继承。

public final class String {
}

像 Java 里的 String、Integer 等类,都是 final 类。 它们不能被继承,也就避免了子类通过重写改变原有行为。

类比理解: 这是一份最终订单,已经确定,不能再派生出新的版本。

注意:

  • final 类不能是 abstract。
  • final 类不能被继承。
  • final 类里的方法从效果上不能被子类重写,因为没有子类。
  • 但 final 类不等于不可变类。

常见误用:

public final class User {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}

这个类虽然是 final,但对象内容仍可变。
final class 只禁止继承,不保证不可变。

真正的不可变类通常要:

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles); // 防御性拷贝
    }

    public String name() {
        return name;
    }

    public List<String> roles() {
        return roles; // List.copyOf 已是不可变
    }
}

四、AI 最爱踩的坑

坑 1:把 final 当成线程安全

final 能保证 “变量引用不会变”,但不等于对象本身线程安全。

final List<String> list = new ArrayList<>();

// 线程不安全:多个线程同时 add,仍然可能出问题
list.add("value");
坑 2:以为 final 一定能提升性能

基础学习阶段,不要把 “性能提升” 作为使用 final 的主要理由。 更重要的是:你是否真的需要不可变引用、固定方法或禁止继承。

坑 3:为了 “安全” 到处加 final

不是所有变量都该加 final。 如果变量本来就会被合理重新赋值,硬加 final 只会让代码更别扭。

final 常见面试辨析

表格

用法含义常见误区
final 变量不能再次赋值误以为对象内容也一定不可变
final 方法不能被子类重写误以为主要是为了性能
final 类不能被继承误以为所有类都该加 final

✅ 核心总结

  1. final 变量:一旦赋值,不能再指向别的对象或值。
  2. final 方法:不能被子类重写,用于固定行为。
  3. final 类:不能被继承,用于终止继承体系。
  4. final 不等于线程安全,也不要把性能当成主理由。
  5. 不要为了 “看起来安全” 到处加 final,只在真正需要限制时使用。

一句话总结:

final 是设计层面的“封条”:
变量封的是“再赋值”,方法封的是“再重写”,类封的是“再继承”。
它不是并发层面的锁,也不自动带来不可变和线程安全。

📚系列专栏:后端基础拾光集
本系列避开教科书式八股讲解,生活化案例讲解 Java 底层原理,覆盖面试、生产开发高频知识点。
已更新:集合底层源码、红黑树、泛型、Java 异常、Integer 缓存池、equals & hashCode、static、Spring 事务七大陷阱。
专栏持续更新 Java 后端基础、源码、面试踩坑干货,欢迎关注一起学习。
如有疑问欢迎评论区留言讨论。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐