← 返回题目列表

final 关键字有什么作用?final、finally、finalize 有什么区别?

高频 简单 第 3 / 32 题 更新于 2026/07/25
Java 基础final关键字

简化版

final 表示「不可变」,修饰变量(值/引用不能再改)、方法(不能被重写)、(不能被继承)。而 finalfinallyfinalize 是三个八竿子打不着的东西:final 是修饰符,finally 是异常处理里「总会执行」的代码块,finalize 是对象被回收前调用的方法(已废弃)。名字像,作用毫无关系。

详细版

final 的三种用法

  • 修饰变量:变量一旦赋值就不能再改。基本类型是不能变;引用类型是引用不能变(但对象内容可以变)。
final int x = 1;          // x 不能再赋值
final List<String> list = new ArrayList<>();
list.add("a");            // ✅ 对象内容能改
list = new ArrayList<>(); // ❌ 编译错误,引用不能改指向
  • 修饰方法:该方法不能被子类重写(override),常用于锁定关键逻辑。
  • 修饰类:该类不能被继承,如 StringInteger 都是 final 类,防止被破坏行为。

final / finally / finalize 的区别

名称是什么作用
final修饰符修饰变量/方法/类,表示不可变/不可重写/不可继承
finally异常处理代码块try-catch-finally无论是否异常都会执行,用于释放资源
finalizeObject 的方法对象被 GC 回收前调用,已废弃(Java 9 起 deprecated),别用

完整版教学

一、final 修饰引用类型:最容易踩的坑

初学者常误以为「final 的对象内容也不能改」,其实 final 锁的是引用(指向哪个对象),不是对象的内容

final StringBuilder sb = new StringBuilder("a");
sb.append("b");          // ✅ 内容变成 "ab",合法
sb = new StringBuilder(); // ❌ 想让 sb 指向新对象,编译报错

想让对象内容也不可变,得靠对象本身设计成不可变(像 String),final 帮不了这个忙。final 只保证「引用不变」,不保证「对象不变」。

二、final 的三大好处

  1. 不可变、线程安全final 字段一旦构造完成就不再变,天然线程安全,多线程读无需同步。这也是为什么不可变对象(String、包装类)适合并发共享。
  2. 语义清晰、防误改:把「不该变的东西」标成 final,编译器帮你拦住意外修改,代码意图也更明确(比如常量 public static final)。
  3. 配合 JMM 的可见性保证:正确构造的 final 字段,在对象发布后对其他线程可见,无需额外同步(这是 JMM 对 final 的特殊保证)。

补充:局部变量被 lambda 或匿名内部类捕获时,必须是 final 或「事实上 final(effectively final,赋值后没再改)」——因为捕获的是值的副本,不允许它变来变去。

三、finally 的执行规则与陷阱

finally 的核心承诺是无论 try 里是否抛异常、是否 return,它都会执行,所以用来释放资源(关连接、关文件):

try {
    return 1;
} finally {
    System.out.println("总会执行");   // 即使上面 return 了也会先执行这里
}

两个陷阱:

  • 别在 finally 里 return 或抛异常:会覆盖 try 里的返回值/异常,导致真正的结果/异常丢失。
try { return 1; }
finally { return 2; }   // ❌ 实际返回 2,try 的 1 被吃掉了
  • 资源释放优先用 try-with-resources:它自动关闭 AutoCloseable 资源,比手写 finally 更安全(详见「受检异常和运行时异常的区别」那道题)。

四、finalize 为什么被废弃

finalize() 本意是「对象被回收前给它一次清理机会」,但它问题太多:调用时机不确定(可能永远不执行)、拖慢 GC、可能让对象「复活」、异常被吞。所以 Java 9 起标记为废弃,官方推荐用 try-with-resourcesCleaner 替代。面试记住结论:finalize 不要用

五、编译期常量与 final 字段的发布语义

并非所有 final 变量都是编译期常量。final int A = 10static final String NAME = "api" 可以在编译期直接内联到调用方字节码;而 final int B = random.nextInt() 只保证赋值一次,值仍要到运行期才能确定。正因为可能被内联,修改公共常量后只重新编译提供方,旧调用方仍可能保留旧值,所以公共协议常量变更要整体重新构建。

static final int TIMEOUT_SECONDS = 30;       // 编译期常量,可被内联
static final Integer LIMIT = 100;            // 包装对象,不是常量表达式
final UUID requestId = UUID.randomUUID();    // 运行期赋值一次

JMM 还给正确构造的 final 字段提供特殊可见性:构造器结束前正常写入的 final 值,在对象没有从构造器中逸出的前提下,被其他线程看到时不会表现成默认值。它解决的是初始化安全,不代表引用指向的可变集合自动线程安全。

说法是否正确边界
final 基本类型不能重新赋值可在声明处或构造路径中赋值一次
final 引用指向的对象不能变化不能换引用,但对象内部仍可变
final 字段天然使整个对象线程安全还要约束其他可变状态和发布方式
static final 一定是编译期常量初始化式必须是常量表达式且类型适合

六、常见误区与追问

  • 误区:认为 final List 的元素不能修改。 final 只锁定变量保存的引用,列表仍可 add
  • 误区:把 finalfinallyfinalize 当作一套机制。 它们分别属于声明、异常控制流和已废弃的对象终结机制。
  • 误区:在 finally 中返回。 它会覆盖 try 的返回值或正在传播的异常,造成排障困难。
  • 追问:final 方法能否重载? 可以;它禁止子类重写同一签名,不妨碍同类声明不同参数列表的方法。
  • 追问:finally 是否绝对执行? 进程被强制终止、JVM 崩溃等情况下不能保证,正常控制流下也应优先使用 try-with-resources 管理资源。
  • 追问:final 字段是否等于不可变对象? 不是;不可变对象还要求不暴露可变内部状态、没有修改入口,并通常禁止可破坏语义的继承。

记忆钩子:final 管的是“这个名字还能不能换绑定”,对象内容能否变化要继续看对象自己的设计。

七、加强记忆

从三种修饰目标串联 final:变量只能完成一次绑定,方法锁定继承扩展点,类关闭继承入口;对引用变量尤其要分清“引用不变”与“对象不变”。finally 是异常控制流中的清理保障,但不应 return;finalize() 则因不确定、慢和可复活等问题被淘汰。再进一层,编译期常量可能被内联,final 字段拥有初始化安全语义,这些都不等于整个对象自动不可变或线程安全。