发布于2026-05-27 阅读(0)
扫一扫,手机访问
但凡经历过Ja va面试,大概率都逃不过这个经典“三连问”:“请说说final、finally和finalize的区别”。这三个词长得太像了,以至于很多初学者甚至工作一两年的开发者,都容易把它们搞混。
但话说回来,能把这三者彻底分清,恰恰是检验Ja va基础是否扎实的一块试金石。它们背后代表了Ja va语言设计的不同维度:一个是关于“不变性”的约束,一个是关于“可靠性”的保证,还有一个则是关于“生命周期”的历史遗留问题。
今天,我们就来把这三个“final家族”成员彻底拆解清楚。从定义、用法、底层机制到最佳实践,一次性讲透,并附上流程图和对比表格,帮你建立清晰的知识图谱。
在深入细节之前,先通过下面这张表,快速抓住它们最核心的不同点。这张表值得你多看两眼,因为它基本概括了问题的全部要点。
| 关键字 | 所属范畴 | 主要作用 | 典型使用场景 |
|---|---|---|---|
| final | 关键字/修饰符 | 限制类、方法、变量不可变 | 常量定义、防止继承/重写 |
| finally | 异常处理块 | 无论是否异常,保证代码执行 | 释放资源(关闭文件、数据库连接等) |
| finalize | Object 类方法 | 对象被垃圾回收前调用(已过时) | 对象临终清理(不推荐) |
首先登场的是final,它是一位“规则制定者”。作为Ja va语言中的一个修饰符,它的核心使命就是定义“不可变性”。一旦被它盯上,无论是类、方法还是变量,其特性就被“锁定”了。
当一个类被声明为final时,就意味着它断绝了“子孙后代”。任何尝试继承它的行为都会在编译期被无情地阻止。这种设计通常用于那些功能完整、设计上不允许被修改或扩展的核心类。
最典型的例子就是String类。想象一下,如果String能被随意继承和修改,那Ja va世界的字符串安全将荡然无存。
public final class Constants {
// 类体
}
// 编译错误:无法继承 final 类
// class MyConstants extends Constants { }
如果final用在方法上,那么这个方法就成了父类中的“祖传手艺”,子类只能继承和使用,但不能重写(Override)。这常用于保护那些关键的业务逻辑或算法实现,防止被子类意外篡改。
class Parent {
public final void doSomething() {
System.out.println("Parent action");
}
}
class Child extends Parent {
// 编译错误:无法重写 final 方法
// public void doSomething() { }
}
这是final最常用也最容易让人困惑的地方。它修饰变量时,效果因变量类型而异:
final int MAX_COUNT = 100; // 常量 final Listlist = new ArrayList<>(); list.add("hello"); // ✅ 允许(对象内容可变) // list = new ArrayList<>(); // ❌ 编译错误,引用地址不可变
最佳实践:对于全局常量,通常结合static final一起使用,并且命名遵循全大写、下划线分隔的规范,比如MAX_THREAD_COUNT。
如果说final是关于空间的约束,那么finally就是关于时间的承诺。它是Ja va异常处理机制中的“定海神针”,与try和catch组成黄金搭档。
它的核心承诺是:无论try块里的代码是顺利执行,还是中途“翻车”抛出异常,finally块中的代码都一定会被执行(当然,前提是JVM没有退出或者线程没被强行中断)。
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 处理异常
} finally {
// 一定会执行的代码,通常用于释放资源
// 例如:关闭文件流、数据库连接、网络连接等
}
这是finally的“主场”。在Ja va 7之前,关闭资源的标准姿势就是在finally块里进行,确保资源在任何情况下都能被释放,避免内存泄漏。
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 读取文件...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当然,现在更推荐使用Ja va 7引入的try-with-resources语法,它能自动关闭实现了AutoCloseable接口的资源,代码简洁又安全。但理解finally,依然是掌握异常处理和资源管理的基础。
即便是“最后防线”,也有几个特殊情况需要留意:
try或catch里执行了System.exit(0),JVM直接退出,finally也就没机会执行了。finally块里也抛出了异常,那么这个异常会“覆盖”掉try或catch里抛出的原始异常,这有时会让问题排查变得困难。最后这位finalize,可以说是Ja va家族里的“悲情角色”。它本是ja va.lang.Object类中定义的一个protected方法,设计初衷是好的:在垃圾回收器(GC)决定回收一个对象的内存之前,给它一个“临终遗言”的机会,用来释放一些本地资源(比如打开的文件句柄)。
public class MyResource {
@Override
protected void finalize() throws Throwable {
try {
// 释放本地资源(如关闭文件句柄)
System.out.println("finalize called");
} finally {
super.finalize();
}
}
}
理想很丰满,现实很骨感。finalize()在实际应用中暴露出一系列致命问题,最终导致它在Ja va 9中被正式标记为deprecated(弃用)。下面这张表清晰地列出了它的“罪状”:
| 问题 | 说明 |
|---|---|
| 不确定性 | GC 何时调用 finalize() 不可预知,甚至永远不会被调用(如果对象永远不被 GC)。 |
| 性能开销 | 重写 finalize() 会显著增加 GC 开销(对象需要两次标记)。 |
| 顺序问题 | finalize() 执行顺序不受控制,可能导致资源访问冲突。 |
| 替代方案 | 使用 try-with-resources、Cleaner(Ja va 9+)或显式资源管理方法(如 close())。 |
所以,现在的行业共识非常明确:永远不要在新代码中重写finalize()方法。

从上面的流程图可以清晰地看到,一个对象从被判定为“可回收”到真正被GC回收,中间可能经历finalize()这个不确定的环节。更离谱的是,在finalize()方法中,对象甚至可以通过将自己重新赋值给某个静态变量来实现“复活”,这种反模式行为极其危险,也是它被抛弃的重要原因之一。
为了更直观地区分,我们把它们放在一起,从多个维度进行横向对比:
| 维度 | final | finally | finalize |
|---|---|---|---|
| 类型 | 关键字 | 关键字 | 方法名 |
| 所在包 | 语言核心 | 语言核心 | ja va.lang.Object |
| 主要作用 | 定义不可变性 | 异常后清理 | 对象临终处理(已过时) |
| 可修饰目标 | 类、方法、变量 | 代码块 | 对象实例方法 |
| 是否推荐使用 | ✅ 大量使用 | ✅ 推荐 | ❌ 强烈不推荐 |
如果觉得概念太多记不住,可以试试这个口诀:final 定终(终态)身不二,finally 生死不离,finalize 来生难觅。
解释一下:
final 锁定类的继承、方法的重写、变量的改变,一旦确定,终身不变。finally 无论程序是正常执行(生)还是抛出异常(死),它都会不离不弃地执行。finalize 被垃圾回收调用,但时机飘忽不定,如今在Ja va新版本中更是难觅踪影。理论讲完了,来看一个把它们放在一起的代码示例(再次强调,finalize仅为演示,实际项目请勿使用):
public class TestFinalFinallyFinalize {
// final 常量
private static final String MESSAGE = "Hello";
public static void main(String[] args) {
// final 局部变量
final int times = 1;
try {
System.out.println(MESSAGE + " for " + times + " time");
// 模拟异常
// throw new RuntimeException();
} finally {
System.out.println("finally block: always executed");
}
}
// 不推荐重写 finalize
@Override
protected void finalize() throws Throwable {
System.out.println("finalize called (deprecated)");
super.finalize();
}
}
程序正常运行的输出会是:
Hello for 1 time
finally block: always executed
至于finalize()方法,你大概率是看不到它被调用的,因为程序很快就结束了,对象可能根本没机会被垃圾回收。
好了,我们来做个最终盘点:
try-with-resources或Cleaner来管理资源。彻底分清这三者,你的Ja va基础就算过了重要一关。最后给个面试小贴士:如果面试官问到finalize,除了讲清楚它的原理和问题,一定要主动提一句它已被废弃,并说明现代的替代方案。这一个小小的细节,就能体现出你对Ja va语言发展的跟进程度。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8