1. 概述

在 Java 中,finalfinallyfinalize 这三个关键字看起来拼写相似,但功能完全不同,属于典型的“名字像,用途差十万八千里”类型。本文将帮你彻底理清它们各自的职责和使用场景,避免在面试或踩坑时搞混。

简单粗暴总结一下:

  • 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; // 编译错误!
    }

}

上面代码中,field2final 的,因此在构造器中再次赋值会直接报错 ❌。

再看方法参数的情况:

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 中有 returnfinally 仍会执行(先暂存返回值,执行完 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

⚠️ 但是!这里有几点致命问题:

  1. 不保证执行System.gc() 只是建议,JVM 可能根本不理你
  2. 执行时机不可控:可能很久以后才触发,甚至不触发
  3. 性能开销大:每个对象都要进 finalize 队列,拖慢 GC
  4. 已废弃:从 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


原始标题:Differences between Final, Finally and Finalize in Java