问题
final、finally、finalize 有何区别?
答案
核心概念
这三个关键字虽然名称相似,但用途完全不同:
final:关键字,用于声明不可变的类、方法或变量finally:异常处理机制的一部分,用于定义必定执行的代码块finalize:Object 类的方法,用于对象被垃圾回收前的清理工作(已废弃)
1. final 关键字
作用范围
| 修饰对象 | 作用 | 示例 |
|---|---|---|
| 类 | 该类不能被继承 | final class String |
| 方法 | 该方法不能被重写 | public final void show() |
| 变量 | 该变量只能赋值一次(常量) | final int MAX = 100 |
典型应用
// 1. final 修饰类:不可继承
public final class ImmutableClass {
// String、Integer 等包装类都是 final 类
}
// 2. final 修饰方法:不可重写
public class Parent {
public final void show() {
System.out.println("不可被子类重写");
}
}
// 3. final 修饰变量
public class FinalDemo {
// 常量:必须初始化,不可修改
private final int MAX_SIZE = 100;
// final 引用:引用不可变,但对象内容可变
private final List<String> list = new ArrayList<>();
public void test() {
// list = new ArrayList<>(); // 编译错误:不能重新赋值
list.add("可以修改内容"); // 正确:对象内容可变
}
}
性能优化
- 方法内联:final 方法可能被 JVM 内联优化
- 线程安全:final 字段在构造函数完成后对其他线程可见(happens-before 保证)
public class SafePublication {
private final int value;
public SafePublication(int value) {
this.value = value; // final 字段保证安全发布
}
}
2. finally 代码块
核心特性
- 必定执行:无论是否发生异常,finally 块都会执行
- 执行时机:在 try-catch 之后,return 之前执行
典型应用场景
// 资源释放(传统方式)
public void readFile() {
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 读取文件操作
} catch (IOException e) {
e.printStackTrace();
} finally {
// 无论是否异常,都会执行资源释放
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 现代写法:try-with-resources(推荐)
public void readFileModern() {
try (FileInputStream fis = new FileInputStream("file.txt")) {
// 读取文件操作
} catch (IOException e) {
e.printStackTrace();
}
// 自动调用 close(),无需 finally
}
特殊情况
public int testFinally() {
try {
return 1; // 先计算返回值,但不立即返回
} finally {
return 2; // finally 中的 return 会覆盖 try 中的 return
}
// 最终返回 2
}
public int testFinally2() {
int x = 1;
try {
return x; // 返回 x 的副本(值为 1)
} finally {
x = 2; // 修改 x 不影响返回值
}
// 最终返回 1
}
不执行 finally 的极端情况
// 1. System.exit() 终止 JVM
try {
System.exit(0);
} finally {
System.out.println("不会执行");
}
// 2. 守护线程中 JVM 退出
// 3. 无限循环或死锁
// 4. 操作系统强制终止进程
3. finalize 方法
基本概念
@Override
protected void finalize() throws Throwable {
try {
// 对象被 GC 回收前的清理工作
System.out.println("对象即将被回收");
} finally {
super.finalize();
}
}
为什么已废弃(Java 9+)
- 不确定性:无法预测何时执行,甚至可能不执行
- 性能问题:有 finalize 的对象需要两次 GC 才能回收
- 异常处理:finalize 中的异常会被忽略
- 资源泄漏风险:依赖 finalize 释放资源不可靠
替代方案
// 1. try-with-resources(推荐)
try (Connection conn = DriverManager.getConnection(url)) {
// 使用连接
} // 自动调用 close()
// 2. Cleaner API(Java 9+)
public class ResourceHolder implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
public ResourceHolder() {
this.cleanable = cleaner.register(this, new CleanupAction());
}
@Override
public void close() {
cleanable.clean();
}
private static class CleanupAction implements Runnable {
@Override
public void run() {
// 清理资源
}
}
}
// 3. 显式 close 方法
public class Resource implements AutoCloseable {
@Override
public void close() {
// 释放资源
}
}
对比总结
| 特性 | final | finally | finalize |
|---|---|---|---|
| 类型 | 关键字 | 关键字 | 方法 |
| 作用 | 声明不可变 | 异常处理必执行代码 | GC 前清理(已废弃) |
| 使用场景 | 常量、不可继承类、不可重写方法 | 资源释放、清理工作 | 不推荐使用 |
| 执行时机 | 编译期/运行期 | try-catch 之后 | GC 回收前(不确定) |
| 性能影响 | 可能优化性能 | 无明显影响 | 降低 GC 性能 |
答题总结
面试要点:
- final:修饰类(不可继承)、方法(不可重写)、变量(不可修改),常用于常量定义和线程安全
- finally:异常处理的一部分,保证资源释放等清理代码必定执行,现代开发推荐 try-with-resources
- finalize:已废弃的 GC 回收前钩子方法,存在不确定性和性能问题,应使用 AutoCloseable 或 Cleaner 替代
记忆技巧:
- final = 最终的(不可变)
- finally = 最终执行(必定执行)
- finalize = 最终化(对象生命周期结束,但已过时)