深入理解Java中关键字final、finally、finalize的区别
作者:身如柳絮随风扬
1. 引言
在 Java 面试中,有一个经典问题频繁出现:“请说说 final、finally 和 finalize 的区别”。这三个关键字拼写相似,但含义和作用截然不同。理解它们的区别,不仅有助于写出更健壮的代码,也是衡量 Java 基础是否扎实的重要标尺。
本文将从定义、使用场景、代码示例、底层机制以及最佳实践等多个维度,彻底讲清这三者的差异,并附上流程图和对比表格,助你一次搞懂。
2. 三者的核心区别速览
| 关键字 | 所属范畴 | 主要作用 | 典型使用场景 |
|---|---|---|---|
| final | 关键字/修饰符 | 限制类、方法、变量不可变 | 常量定义、防止继承/重写 |
| finally | 异常处理块 | 无论是否异常,保证代码执行 | 释放资源(关闭文件、数据库连接等) |
| finalize | Object 类方法 | 对象被垃圾回收前调用(已过时) | 对象临终清理(不推荐) |
3. final:不可变的守护者
final 是一个修饰符,可以用于修饰类、方法、变量。一旦被 final 修饰,其特性便“固定”下来。
3.1 final 修饰类
被 final 修饰的类不能被继承。这通常用于那些设计上不允许扩展的类,例如 String、Integer 等包装类。
public final class Constants {
// 类体
}
// 编译错误:无法继承 final 类
// class MyConstants extends Constants { }
3.2 final 修饰方法
被 final 修饰的方法不能被重写(Override)。这可以防止子类修改父类的关键行为。
class Parent {
public final void doSomething() {
System.out.println("Parent action");
}
}
class Child extends Parent {
// 编译错误:无法重写 final 方法
// public void doSomething() { }
}
3.3 final 修饰变量
- 基本类型变量:一旦赋值,值不可改变,成为常量。
- 引用类型变量:引用地址不可改变,但对象内部状态仍可修改。
- 成员变量:必须在声明时、构造器或初始化块中显式赋值。
- 局部变量:在使用前赋值即可,之后不可修改。
final int MAX_COUNT = 100; // 常量
final List<String> list = new ArrayList<>();
list.add("hello"); // ✅ 允许(对象内容可变)
// list = new ArrayList<>(); // ❌ 编译错误,引用地址不可变
最佳实践:使用 static final 定义真正的全局常量(命名全大写、下划线分隔)。
4. finally:保证执行的最后防线
finally 是异常处理机制的一部分,与 try 和 catch 配合使用。它的特点是:无论 try 块中是否发生异常,finally 块中的代码都会被执行(除非 JVM 退出或当前线程被中断)。
4.1 基本语法
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 处理异常
} finally {
// 一定会执行的代码,通常用于释放资源
// 例如:关闭文件流、数据库连接、网络连接等
}
4.2 典型场景:资源释放
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();
}
}
}
注意:Java 7 引入了 try-with-resources,可以更优雅地实现自动资源释放(要求资源类实现 AutoCloseable)。但 finally 仍然是通用异常清理模式的基础。
4.3 特殊情况
- 如果
try或catch中执行了System.exit(0),则finally不会执行。 - 如果
finally块中抛出了未被处理的异常,它会覆盖try或catch中抛出的原始异常。
5. finalize:被遗忘的临终方法
finalize() 是 java.lang.Object 类中定义的一个 protected 方法。当垃圾回收器(GC)确定某个对象“没有引用”时,会先调用该对象的 finalize() 方法(如果被重写),然后再回收对象的内存。
5.1 基本用法(已过时)
public class MyResource {
@Override
protected void finalize() throws Throwable {
try {
// 释放本地资源(如关闭文件句柄)
System.out.println("finalize called");
} finally {
super.finalize();
}
}
}
5.2 为什么 finalize 已被标记为废弃(deprecated)?
| 问题 | 说明 |
|---|---|
| 不确定性 | GC 何时调用 finalize() 不可预知,甚至永远不会被调用(如果对象永远不被 GC)。 |
| 性能开销 | 重写 finalize() 会显著增加 GC 开销(对象需要两次标记)。 |
| 顺序问题 | finalize() 执行顺序不受控制,可能导致资源访问冲突。 |
| 替代方案 | 使用 try-with-resources、Cleaner(Java 9+)或显式资源管理方法(如 close())。 |
自 Java 9 起,finalize() 已被正式标记为 deprecated(弃用),建议永远不要在新代码中使用它。
5.3 对象死亡流程(含 finalize)

从流程图可以看出,finalize() 甚至可能导致对象“复活”(在 finalize() 中重新将自己赋值给某个静态变量),这是非常危险的行为,也是它被弃用的原因之一。
6. 综合对比与记忆口诀
6.1 对比表格
| 维度 | final | finally | finalize |
|---|---|---|---|
| 类型 | 关键字 | 关键字 | 方法名 |
| 所在包 | 语言核心 | 语言核心 | java.lang.Object |
| 主要作用 | 定义不可变性 | 异常后清理 | 对象临终处理(已过时) |
| 可修饰目标 | 类、方法、变量 | 代码块 | 对象实例方法 |
| 是否推荐使用 | ✅ 大量使用 | ✅ 推荐 | ❌ 强烈不推荐 |
6.2 记忆口诀
final 定终(终态)身不二,finally 生死不离,finalize 来生难觅。
解释:
final锁定类的继承、方法的重写、变量的改变。finally无论生死(异常与否)都会执行。finalize被垃圾回收调用,但时机不定,如今已“难觅”踪影。
7. 代码示例:三者的协作
以下示例展示了在一个方法中同时使用 final 变量、finally 块以及 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() 通常不会被调用,因为程序退出时对象可能不一定会被 GC。
8. 总结
- final:不可变修饰符,用来定义常量、阻止继承和重写。
- finally:异常处理中的保证执行块,用于释放资源等清理工作。
- finalize:已被废弃的对象回收前回调方法,不要在新代码中使用。
掌握这三者的区别,是 Java 基础学习中的重要里程碑。最后提醒:如果你面试时被问到 finalize,最好能主动说明它已被标记为过时,并推荐使用 try-with-resources 或 Cleaner 作为替代方案,这将展示你对 Java 现代特性的了解。
到此这篇关于深入理解Java中关键字final、finally、finalize的区别的文章就介绍到这了,更多相关Java final finally finalize区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
