final、finally、finalize 的区别?
结论:三个词只是长得像,语义毫无关联。final 是修饰符,表示"不可再变":类不可被继承、方法不可被重写、变量不可被重新赋值。finally 是异常处理语句块,配合 try 使用,用来放必须执行的收尾逻辑(关闭资源),通常一定会执行。finalize() 是 Object 的方法,GC 回收对象前的一次回调;它已被标记废弃(deprecated),JDK 18 起 finalization 机制被标记为"将被移除"(JEP 421),新代码不应使用。
1. 三者对照
| 关键字 | 类别 | 作用对象 | 语义 | 现状 |
|---|---|---|---|---|
final | 修饰符 | 类、方法、变量、参数 | 不可继承 / 不可重写 / 不可重新赋值 | 常用 |
finally | 语句块 | try 结构 | 收尾代码,正常与异常路径都执行 | 常用,资源关闭优先用 try-with-resources |
finalize | 方法 | 对象 | GC 前回调,做最后清理 | 废弃,禁止使用 |
2. final 的三种用法
final class Config { } // ① 类:不可被继承
class Base {
final void hook() { } // ② 方法:子类不可重写
}
class Demo {
static final int MAX = 100; // ③ 变量:不可二次赋值
final List<String> list = new ArrayList<>();
void add() {
list.add("a"); // 合法:final 约束的是"引用不能再指向别人"
// list = new ArrayList<>(); // 编译错误
}
}
三个容易答错的点:
final引用 ≠ 对象不可变。final List仍可add,只是不能重新赋值;要真正不可变还得配合Collections.unmodifiableList或List.of;static final常量会被内联。编译期常量(如static final int、static final String)在调用方字节码里直接写入字面量,改常量后依赖方要重新编译才能生效,否则仍用旧值;final参数只在方法体内生效,且 Java 的匿名类/lambda 捕获要求变量"有效 final(effectively final)",不必显式加final。
3. finally 的典型写法
public void query() {
Connection conn = null;
try {
conn = dataSource.getConnection();
// ... 业务
} catch (SQLException e) {
log.warn("query failed", e);
} finally {
closeQuietly(conn); // 无论成功、失败、提前 return 都会走到
}
}
// 更推荐:try-with-resources 由编译器生成 close 调用与异常抑制
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(SQL)) {
ps.executeQuery();
}
try-with-resources 本质也是 finally,但会把 close() 抛出的异常作为 suppressed(抑制异常) 挂到主异常上,不会像手写 finally 那样把业务异常吞掉。
4. finalize 为什么被废弃
| 问题 | 说明 |
|---|---|
| 执行时机不确定 | 由 GC 触发,可能几分钟后才执行,也可能整个进程都不执行 |
| 拖慢 GC | 带 finalize 的对象至少要经历两轮回收,还进入 F-Queue 由低优先级线程处理 |
| 可"复活"对象 | finalize 里把 this 赋给静态字段,对象复活,破坏回收语义 |
| 异常被忽略 | finalize 抛出的异常会被丢弃,问题难发现 |
| 替代方案成熟 | try-with-resources、java.lang.ref.Cleaner(JDK 9+) |
// 反例:用 finalize 释放文件句柄
@Override
@Deprecated(since = "9", forRemoval = true)
protected void finalize() throws Throwable {
file.close(); // 可能永远不执行;异常会被吞掉
super.finalize();
}
5. 常见追问
| 追问 | 回答要点 |
|---|---|
final 能修饰构造器吗? | 不能,构造器不能被继承或重写,没有意义 |
final 方法能被重载吗? | 能,重载(overload)是新方法,与是否 final 无关 |
finally 和 finalize 有什么关系? | 没关系,只是名字相似 |
什么时候用 final? | 常量、不可变字段、模板方法中不允许子类改写的钩子、参数防误改 |
为什么单例常用 static final? | 类初始化时创建,JVM 保证线程安全且只执行一次 |
Cleaner 比 finalize 好在哪? | 独立清理线程、可注册多个清理动作、不依赖对象复活,但仍不保证及时执行 |
再补一个面试常问的对比:finally 里的代码"通常"会执行,但并不绝对——System.exit()、JVM 崩溃、守护线程退出都会跳过 finally,这部分在下一篇展开。
小结
final 管"不可变",finally 管"必执行",finalize 管"回收前回调"且已被放弃。写代码时:字段能 final 就 final,资源关闭用 try-with-resources,任何场景都不要重写 finalize。