final、finally、finalize 的区别?

2025年 阅读约 7 分钟 面试指南 · Java面试

深入解析Java中final、finally、finalize的区别,从基本用法到JVM垃圾回收,分初级、中级、高级三个层次全面讲解。

一句话总结

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)管理直接内存等非堆资源。