Java final、finally、finalize 区别

笔记/Java/Java基础/面向对象编程 (OOP)/Java final、finally、finalize 区别

背景与动机#

finalfinallyfinalize 名字很像,初学者和面试场景里经常被放在一起问,但它们完全不是同一类东西:

  • final关键字,用来限制“能不能改”
  • finally异常处理语法块,用来保证“收尾动作会执行”
  • finalizeObject 里的方法,和垃圾回收有关,但早已不推荐依赖

如果只记“三个 final 开头”,很容易混。这篇笔记把它们放在一张对照表里,再分别拆开看。

核心对比#

对比项finalfinallyfinalize
类型关键字异常处理语法块Object 实例方法
主要作用限制变量、方法、类被修改无论是否异常,都执行收尾逻辑对象被 GC 回收前做清理(理论上的钩子)
出现位置变量、方法、类声明处必须配合 trytry-catch类中重写,由 JVM 在特定时机调用
是否必须手写按需使用按需使用一般不应再依赖
现代 Java 建议常量、不可继承类、不可重写方法时正常使用资源释放优先用 try-with-resources已废弃,避免使用

记忆口诀可以记成:

  • final 管“定不变”
  • finally 管“必收尾”
  • finalize 管“回收前” —— 但这条在现代 Java 里已经过时

final:限制修改#

final 修饰的目标不同,含义也不同:

修饰目标含义
变量只能赋值一次,之后不能重新指向新值
方法子类不能重写这个方法
这个类不能被继承
final class Config {
final String env;
Config(String env) {
this.env = env;
}
final void printEnv() {
System.out.println(env);
}
}

上面这段里:

  • final class Config:别人不能再写 class DevConfig extends Config
  • final String env:构造完成后不能再给 env 重新赋值
  • final void printEnv():子类不能重写 printEnv()

注意:final 修饰引用类型时,限制的是“引用不能改”,不是“对象内部状态不能改”。

final java.util.List<String> names = new java.util.ArrayList<>();
names.add("java"); // 可以
names = new java.util.ArrayList<>(); // 编译错误

更完整的用法见 Java 封装、继承与多态Java 变量与基本数据类型

finally:异常处理里的收尾块#

finally 必须出现在 try 后面,用来放无论是否发生异常都要执行的逻辑,常见是关闭资源、写收尾日志。

java.io.BufferedReader reader = null;
try {
reader = java.nio.file.Files.newBufferedReader(java.nio.file.Path.of("a.txt"));
System.out.println(reader.readLine());
} catch (java.io.IOException e) {
System.out.println("read fail: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
}

几个边界要记住:

  • finally 不是“处理异常”,而是“做收尾”
  • 通常会在 return 之前执行,所以不建议在 finally 里写复杂返回逻辑
  • 如果 JVM 直接退出、进程崩溃,finally 也可能没机会执行

现代 Java 里,关闭文件、流、连接这类资源,更推荐 try-with-resources

try (java.io.BufferedReader reader =
java.nio.file.Files.newBufferedReader(java.nio.file.Path.of("a.txt"))) {
System.out.println(reader.readLine());
} catch (java.io.IOException e) {
System.out.println("read fail: " + e.getMessage());
}

更完整的异常处理见 Java 异常处理

finalize:GC 回收前的钩子(已废弃)#

finalizeObject 定义的 protected 方法:

protected void finalize() throws Throwable

理论上,对象失去引用、即将被垃圾回收前,JVM 可能调用一次 finalize(),让开发者做最后清理。

class TempFileHolder {
private final String path;
TempFileHolder(String path) {
this.path = path;
}
@Override
@Deprecated(since = "9", forRemoval = true)
protected void finalize() {
System.out.println("maybe cleanup: " + path);
}
}

但它在实际开发里问题很多:

  • 调用时机不确定:对象什么时候被回收,JVM 说了算,finalize() 可能很晚才执行,也可能根本不执行
  • 性能差:会拖慢 GC,历史上还可能导致对象“复活”
  • 不可靠:不能把它当成资源释放机制
  • 已被废弃:Java 9 起标记 @Deprecated,后续版本继续推进移除

所以现代 Java 的正确做法是:

  • 文件、连接、锁:用 try-with-resources 或显式 close()
  • 需要更灵活的清理:Java 9+ 可以用 java.lang.ref.Cleaner
  • 业务对象生命周期:由应用代码自己管理,不要等 GC

Object 其他常用方法见 Java Object 类

最小可运行示例:三者同框#

public class FinalDemo {
public static void main(String[] args) {
System.out.println(readWithFinally());
}
static int readWithFinally() {
try {
return 1;
} finally {
System.out.println("finally runs before return");
}
}
}
final class Demo {
final int value = 42;
}

运行 FinalDemo 会先打印 finally runs before return,再结束方法。

这段代码里:

  • final class Demofinal int value 体现 final
  • finally 块体现收尾执行时机
  • 没有演示 finalize,因为正常项目里不应再依赖它

常见陷阱#

1. 把 finalize 当成析构函数#

C++ 的析构函数会在对象离开作用域时确定执行;Java 的 finalize() 没有这种保证。资源释放必须在业务代码里完成,不能等 GC。

2. 以为 final 修饰对象后,对象内容也不能变#

final 只锁引用,不锁对象内部状态。集合、数组、自定义对象字段仍可能继续变化。

3. 在 finally 里写业务分支#

finally 适合关资源、打日志,不适合塞复杂业务判断。否则 trycatchfinally 三条路径交织,代码很难读。

4. 面试只背名字,不背“属于哪一层”#

正确答法要先分类型:

  • 关键字 → final
  • 语法块 → finally
  • 方法 → finalize

面试高频问题#

1. finalfinallyfinalize 有什么区别?#

final 是关键字,限制变量、方法、类不能再被修改;finally 是异常处理语法块,保证收尾逻辑通常都会执行;finalizeObject 方法,和 GC 回收有关,但已废弃,不应再用于资源释放。

2. finally 一定会执行吗?#

大多数正常路径下会执行,包括 trycatch 里遇到 return 的情况。极端情况下,比如 System.exit()、JVM 崩溃,finally 可能不执行。

3. 为什么不再推荐使用 finalize#

因为它执行时机不可控、性能开销大、容易误导开发者把资源释放交给 GC。现代 Java 更推荐 try-with-resourcesAutoCloseableCleaner

4. final 修饰的基本类型和引用类型有什么区别?#

基本类型:final 后值不能改。引用类型:final 后引用不能指向新对象,但对象本身的状态仍可能改变。

一句话总结#

final 定规则、finally 做收尾、finalize 曾是 GC 钩子但已过时;面试和写代码时,先分清“关键字、语法块、方法”这三层,再谈具体场景。

文章目录

文章目录