一句话总结
final 修饰不可变(类不可继承/方法不可重写/变量不可改引用)→ finally 必执行(除非 System.exit() 或 JVM 崩溃)→ finalize 已废弃(JDK 9 @Deprecated,用 Cleaner 或 try-with-resources 替代)。三者只是名字像,功能完全无关。
初级理解
这三个关键字虽然长得像,但功能完全不同,没有任何关系:
final — 修饰符(不可变):
1. 修饰类:类不能被继承。例如 String 类就是 final 的。
2. 修饰方法:方法不能被重写。例如 Object 的 getClass() 方法。
3. 修饰变量:基本类型值不可变,引用类型引用不可变(但对象内容可变)。
finally — 异常处理(必执行):try-catch-finally 中的 finally 块,无论是否发生异常都会执行。通常用于释放资源(关闭流、连接等)。
finalize — 垃圾回收(已废弃):Object 类的方法,在对象被 GC 回收前调用。JDK 9 已标记为 @Deprecated,不推荐使用。
// final 修饰变量
final int x = 10;
// x = 20; // 编译错误
final List<String> list = new ArrayList<>();
list.add("hello"); // 可以修改对象内容
// list = new ArrayList<>(); // 编译错误,不能改变引用
// finally 块
try {
// 可能抛异常的代码
} catch (Exception e) {
// 异常处理
} finally {
// 无论如何都会执行
}
中级深入
finally 不执行的情况:虽然 finally 号称"一定执行",但以下情况不会执行:
1. 在 try 或 catch 中调用 System.exit():JVM 直接退出,finally 不会执行。
2. 守护线程中所有非守护线程结束:JVM 退出,守护线程的 finally 不会执行。
3. try 或 catch 中发生无限循环或死锁:代码卡住,永远到不了 finally。
try {
System.out.println("try block");
System.exit(0); // JVM 退出
} finally {
System.out.println("finally block"); // 不会执行!
}
finally 中的 return 会覆盖 try 中的 return:
public static int test() {
try {
return 1;
} finally {
return 2; // 最终返回 2,覆盖了 try 的返回值
}
}
final 修饰局部变量的"有效final":JDK 8 引入了 effectively final 概念。如果一个局部变量在初始化后没有被修改,即使没有显式声明 final,编译器也会将其视为 final,可以在 Lambda 表达式中使用。
int x = 10; // 没有 final 修饰
// x = 20; // 如果取消注释,x 就不再是 effectively final
Runnable r = () -> System.out.println(x); // 可以使用
高级拓展
finalize() 为什么被废弃?
1. 执行时机不确定:对象变成垃圾后,finalize() 不会立即执行,甚至可能永远不执行(因为对象可能从不被 GC)。
2. 性能极差:含有 finalize() 的对象 GC 时需要两次标记,第一次标记后放入 F-Queue 等待执行 finalize(),执行完后才能第二次标记回收。
3. 可能导致对象"复活":在 finalize() 中将 this 赋值给某个静态变量,对象就"复活"了,不会被回收,但 finalize() 只会被调用一次。
替代方案:使用 try-with-resources(JDK 7)或 Cleaner(JDK 9)来管理资源释放。
// try-with-resources:自动关闭资源
try (FileInputStream fis = new FileInputStream("test.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 使用流
} // 自动调用 close(),无需 finally
// Cleaner(JDK 9+)
public class MyResource implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
public MyResource() {
this.cleanable = cleaner.register(this, () -> {
// 清理逻辑
});
}
@Override
public void close() {
cleanable.clean();
}
}
final 与 JVM 优化:final 方法可以被 JIT 编译器内联优化,因为不需要处理子类重写的情况。final 类中的所有方法都隐式是 final 的。
面试加分项:能说出 finalize() 被废弃的原因和 Cleaner 替代方案,说明你关注 JDK 最新发展。
实战场景
场景:finally 中资源关闭的正确姿势
// 反例:嵌套 try-catch,代码臃肿
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 使用 fis
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try { fis.close(); } catch (IOException e) { }
}
}
// 正例:try-with-resources(JDK 7+)
try (FileInputStream fis = new FileInputStream("test.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 使用流,自动关闭
} catch (IOException e) {
e.printStackTrace();
}
场景:final 常量的命名规范
// 常量:全大写 + 下划线
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_CHARSET = "UTF-8";
// 不可变集合
private static final Set<String> ALLOWED_ORIGINS =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a.com", "b.com")));
面试模拟
Q:finally 一定会执行吗?
A:不一定。以下情况不会执行:1) System.exit() 在 try/catch 中被调用;2) 守护线程中所有非守护线程结束,JVM 退出;3) try/catch 中发生死循环或死锁。另外,finally 中的 return 会覆盖 try 中的 return 值。
Q:finalize() 为什么被废弃?替代方案是什么?
A:1) 执行时机不确定,可能永远不执行;2) 性能差,需要两次 GC 标记;3) 可能导致对象"复活"。替代方案:try-with-resources(JDK 7)管理 IO 资源,Cleaner(JDK 9)管理直接内存等非堆资源。