1. 概述
在 Java 中,final
、finally
和 finalize
这三个关键字看起来拼写相似,但功能完全不同,属于典型的“名字像,用途差十万八千里”类型。本文将帮你彻底理清它们各自的职责和使用场景,避免在面试或踩坑时搞混。
简单粗暴总结一下:
- ✅
final
:用于定义不可变性,可以修饰类、方法、变量等 - ✅
finally
:异常处理中的兜底代码块,无论是否抛异常都会执行 - ✅
finalize
:对象回收前的最后告别仪式(但已被淘汰)
下面我们逐个拆解。
2. final
关键字
final
是 Java 中用来实现“不可变”语义的关键字。它可以修饰:
- 类(class)
- 方法(method)
- 字段(field)
- 局部变量(local variable)
- 方法参数(parameter)
⚠️ 注意:虽然都叫 final
,但它在不同上下文中的作用是不一样的。
2.1 final
字段、参数与变量
当一个字段、变量或参数被声明为 final
,意味着一旦赋值就不能再改。
来看一个例子:
public class Parent {
int field1 = 1;
final int field2 = 2;
Parent() {
field1 = 2; // OK
field2 = 3; // 编译错误!
}
}
上面代码中,field2
是 final
的,因此在构造器中再次赋值会直接报错 ❌。
再看方法参数的情况:
void method1(int arg1, final int arg2) {
arg1 = 2; // OK
arg2 = 3; // 编译错误!
}
同理,arg2
被标记为 final
,无法重新赋值。
局部变量也一样:
void method2() {
final int localVar = 2; // OK
localVar = 3; // 编译错误!
}
📌 小贴士:final
只保证引用不变,不保证对象内容不变。比如:
final List<String> list = new ArrayList<>();
list.add("hello"); // ✅ 允许
list = new ArrayList<>(); // ❌ 不允许
所以,final
并不能让你的对象真正“不可变”,要实现不可变对象还需配合其他手段(如 Collections.unmodifiableList)。
2.2 final
方法
用 final
修饰的方法不能被子类重写(override)。
继续上面的例子,我们把 method2()
设为 final
:
public class Parent {
final void method2() {
// ...
}
}
然后尝试在子类中重写它:
public class Child extends Parent {
@Override
void method1(int arg1, int arg2) {
// OK,普通方法可重写
}
@Override
final void method2() {
// ❌ 编译错误!final 方法不能重写
}
}
直接报错,编译不过。这是 Java 编译期就帮你拦住的强约束。
2.3 final
类
如果一个类被声明为 final
,那它不能被继承。
比如我们将 Child
类标记为 final
:
public final class Child extends Parent {
// ...
}
此时再想创建子类就会失败:
public class GrandChild extends Child {
// ❌ 编译错误:无法继承 final 类
}
典型应用:String
类就是 final
的,防止被恶意继承篡改行为。
3. finally
块
finally
是异常处理机制的一部分,通常配合 try-catch
使用。它的核心特点是:
✅ 无论是否抛出异常,
finally
块中的代码总会执行(除非 JVM 退出)。
这使得它非常适合用于资源清理,比如关闭文件、连接等。
基本语法结构
try {
// 可能出错的代码
} catch (Exception e) {
// 异常处理
} finally {
// 总会执行的代码
}
来看一个例子:
public static void main(String args[]) {
try {
System.out.println("Execute try block");
throw new Exception();
} catch (Exception e) {
System.out.println("Execute catch block");
} finally {
System.out.println("Execute finally block");
}
}
输出结果:
Execute try block
Execute catch block
Execute finally block
即使去掉 catch
块,只要加上 finally
,也能保证执行:
public static void main(String args[]) throws Exception {
try {
System.out.println("Execute try block");
throw new Exception();
} finally {
System.out.println("Execute finally block");
}
}
输出:
Execute try block
Execute finally block
📌 特殊情况说明:
- 如果
try
中有return
,finally
仍会执行(先暂存返回值,执行完finally
再返回) - 如果
finally
自己也return
,会覆盖try
中的返回值(不推荐这么写,容易踩坑)
✅ 推荐做法:finally
里只做资源释放,不要写 return
或抛异常。
4. finalize
方法
finalize()
是 java.lang.Object
中的一个受保护方法,原型如下:
protected void finalize() throws Throwable
它的设计初衷是:当一个对象被垃圾回收器(GC)回收前,JVM 会自动调用它的 finalize()
方法,相当于给对象一次“临终发言”的机会。
示例代码
我们可以重写它来做一些清理工作(虽然现在不推荐了):
@Override
protected void finalize() throws Throwable {
System.out.println("Execute finalize method");
super.finalize();
}
测试调用:
public static void main(String[] args) throws Exception {
FinalizeObject object = new FinalizeObject();
object = null;
System.gc(); // 建议 JVM 执行 GC
Thread.sleep(1000); // 等待 GC 执行
}
理想情况下输出:
Execute finalize method
⚠️ 但是!这里有几点致命问题:
- 不保证执行:
System.gc()
只是建议,JVM 可能根本不理你 - 执行时机不可控:可能很久以后才触发,甚至不触发
- 性能开销大:每个对象都要进 finalize 队列,拖慢 GC
- 已废弃:从 Java 9 开始,
finalize()
被标记为@Deprecated
✅ 正确替代方案:
- 使用
try-with-resources
自动管理资源 - 实现
AutoCloseable
接口 - 显式调用
close()
方法
📌 总结:finalize()
属于“历史遗留功能”,现在项目中绝对不要使用。
5. 总结对比
关键字 | 作用 | 使用场景 | 是否推荐使用 |
---|---|---|---|
final |
定义不可变性 | 防止继承、重写、修改变量 | ✅ 强烈推荐 |
finally |
异常处理后的兜底执行 | 资源释放、清理操作 | ✅ 推荐(但慎用 return) |
finalize |
对象回收前的回调(已被淘汰) | 无 | ❌ 禁止使用 |
📌 一句话口诀帮助记忆:
final
锁结构,finally
守善后,finalize
已过时。
项目中如果看到 finalize()
,建议直接重构掉,别留隐患。
示例代码已上传至 GitHub:https://github.com/yourname/java-core-examples/tree/main/final-keywords