
作为 Java 开发者,你是不是也曾经对着 “final、finally、finalize” 这三个 “长得像” 的词犯迷糊?明明拼写只差几个字母,作用却天差地别 —— 一个管 “不可变”,一个管 “资源清理”,一个早就被 Java 官方 “打入冷宫”。
今天这篇文章,用表格对比、代码实例、避坑指南帮你彻底分清,看完直接能应对面试和开发!
一、1 张表格秒懂核心区别
先上 “干货总结表”,把三者最关键的差异摆出来,看完心里先有谱:
|
特性 |
final(不可变修饰符) |
finally(异常处理块) |
finalize(对象终结方法) |
|
本质 |
关键字(修饰类 / 方法 / 变量) |
关键字(try-catch 配套块) |
Object 类的方法(JDK9 + 弃用) |
|
核心作用 |
保证 “不可变”,防篡改 |
确保资源必清理(无论异常) |
垃圾回收前 “最后清理” |
|
执行时机 |
编译期检查 + 运行时生效 |
try/catch 执行后必触发(特例除外) |
垃圾回收时随机执行(不确定) |
|
可控性 |
开发者完全掌控 |
开发者完全掌控 |
完全依赖 JVM(不可控) |
|
生产推荐度 |
✅ 强烈推荐(安全 + 线程安全) |
✅ 推荐(现代用 try-with-resources 替代) |
❌ 绝对不推荐(性能差 + 不稳定) |
二、逐个拆解:用法 + 案例 + 避坑
1. final:Java 里的 “不可变锁”
final 是用来 “上锁” 的修饰符,修饰不同对象,锁的效果也不同 —— 但记住:锁的是 “引用” 或 “继承关系”,不是 “对象内容”(这是高频坑!)。
(1)修饰类:禁止继承,像 “密封的盒子”
被 final 修饰的类,不能被任何子类继承,列如 JDK 里的String类就是 final 的,防止被篡改核心逻辑。
// ✅ 正确:String是final类,不能被继承
final class String {
// ... 核心逻辑
}
// ❌ 错误:编译报错,不能继承final类
class MyString extends String {
}
用场景:核心工具类(如java.lang.Math)、安全敏感类,防止子类重写破坏逻辑。
(2)修饰方法:禁止重写,像 “固定的流程”
被 final 修饰的方法,子类不能重写,保证方法行为 “一成不变”。
class Parent {
// final方法:子类不能改
public final void payBill() {
System.out.println("固定支付流程:校验→扣款→对账");
}
}
class Child extends Parent {
// ❌ 错误:编译报错,不能重写final方法
@Override
public void payBill() {
System.out.println("想改支付流程?不行!");
}
}
用场景:模板方法模式中的 “核心步骤”,列如支付、登录的固定流程。
(3)修饰变量:禁止改引用,像 “钉死的指针”
这是最容易踩坑的点!final 修饰变量分两种情况:
- 基本类型(int、String 等):值完全不能改;
- 引用类型(List、Map 等):引用不能改,但对象里的内容能改!
// 案例1:final修饰基本类型(值不能改)
final int age = 20;
age = 21; // ❌ 编译报错:不能改值
// 案例2:final修饰引用类型(引用不能改,但内容能改)
final List<String> list = new ArrayList<>();
list.add("Java"); // ✅ 正确:内容能加
list.add("Python"); // ✅ 正确:内容能加
list = new ArrayList<>(); // ❌ 错误:引用不能改
避坑提醒:想让引用类型 “完全不可变”?得用
Collections.unmodifiableList(),列如:
final List<String> unmodList = Collections.unmodifiableList(list);
unmodList.add("C++"); // ❌ 运行报错:不能改内容
2. finally:资源的 “最后守护者”
finally 是 try-catch 的 “配套工具”,作用只有一个:无论 try 里是否抛异常、是否 return,finally 里的代码必执行(除了 JVM 退出的特例),专门用来清理资源(列如关文件、关数据库连接)。
(1)传统用法:手动关资源
以前没有 try-with-resources 时,靠 finally 保证资源必关:
public void readFile(String path) {
FileInputStream fis = null;
try {
fis = new FileInputStream(path);
// 读文件逻辑
} catch (IOException e) {
// 处理异常
} finally {
// 无论是否异常,必关流
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// 处理关流异常
}
}
}
}
(2)现代用法:try-with-resources(推荐!)
Java 7 + 推出的 “自动资源管理”,替代了繁琐的 finally,资源会自动关闭(只要类实现AutoCloseable接口):
public void readFileModern(String path) {
// 资源在try括号里声明,结束后自动关
try (FileInputStream fis = new FileInputStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// 处理异常
}
// 不用写finally,fis和br自动关!
}
(3)必记特例:这些情况 finally 不执行
只有一种情况会让 finally “失效”—— 调用System.exit(0)(强制退出 JVM):
try {
System.out.println("try执行");
System.exit(0); // 强制退出JVM
} finally {
System.out.println("finally执行"); // ❌ 看不到:JVM已经退了
}
(4)高频坑:finally 里别写 return
如果 finally 里写了 return,会 “覆盖” try 里的 return 值,而且很难排查:
public int getNum() {
try {
return 1; // 这个值会被覆盖
} finally {
return 2; // ❌ 不推荐!实际返回2
}
}
避坑提醒:finally 只用来 “清理资源”,绝对不要写 return、抛异常!
3. finalize:被 Java 官方 “放弃” 的方法
finalize 是 Object 类的一个方法,原本设计是:对象被垃圾回收(GC)前,JVM 会调用它做 “最后清理”(列如释放 native 资源)。但目前已经被 JDK 9 标记为@Deprecated(弃用),缘由很简单:太不靠谱!
(1)为什么不推荐?3 个致命问题
- 执行时机不确定:GC 什么时候跑、会不会调用 finalize,完全由 JVM 决定,可能对象都被回收了,finalize 还没执行;
- 性能极差:会拖慢 GC 速度,甚至可能导致内存泄漏;
- 异常会被吞:finalize 里抛的异常,JVM 会直接忽略,排查问题时找不到痕迹。
(2)替代方案:用这些更靠谱的方式
- 显式 close 方法:列如InputStream.close(),主动调用清理;
- try-with-resources:自动管理资源(前面讲过,最推荐);
- Cleaner 机制:Java 9 + 提供的替代 finalize 的工具,但一般开发用不到(底层框架可能会用)。
(3)反面案例:别再写这种代码!
public class BadFinalize {
@Override
protected void finalize() throws Throwable {
// ❌ 不推荐:依赖finalize清理资源
System.out.println("清理资源");
super.finalize();
}
}
三、实战避坑指南:3 个必记原则
- final 用在 “需要不变” 的场景:成员变量用 final,减少并发问题(final 变量初始化后线程安全);工具类用 final,防止被继承篡改(列如自己写的加密工具类)。
- finally 只做 “资源清理”:别在 finally 里写业务逻辑、return、抛异常;能用电 try-with-resources,就别手动写 finally 关资源。
- finalize 绝对别碰:新项目里看到 finalize,直接重构;清理资源优先用 try-with-resources 或显式 close。
四、面试高频问题:3 个经典问答
Q1:final 修饰的类,真的完全不可变吗?
A:不必定。如果类里有引用类型成员变量,即使类是 final 的,成员变量的内容还是能改(除非成员变量也用 final + 不可变容器,列如
Collections.unmodifiableList())。
Q2:try-with-resources 能同时管理多个资源吗?
A:可以。多个资源用分号隔开,JVM 会按 “声明逆序” 自动关闭(列如先关BufferedReader,再关FileInputStream)。
Q3:为什么 Java 要弃用 finalize?
A:由于 finalize 执行时机不确定、性能差、异常会被吞,无法可靠地清理资源,所以官方推荐用 try-with-resources 等更可控的方式替代。
看完这篇,是不是彻底分清 final、finally、finalize 了?记住:final 管 “不可变”,finally 管 “资源清”,finalize 早 “被放弃”—— 下次面试或开发遇到,再也不会混淆啦!
#java##final##面试##finally#
真不戳💪
受益匪浅👏
final 是「常量/终态」——一次声明终身不变;finally 是「扫尾」——无论抛不抛异常都执行;finalize 是「遗嘱」——对象被回收前给它的最后一次自赎机会。#java#