商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 深入理解Java中关键字final、finally、finalize的区别

深入理解Java中关键字final、finally、finalize的区别

  发布于2026-05-27 阅读(0)

扫一扫,手机访问

1. 引言

但凡经历过Ja va面试,大概率都逃不过这个经典“三连问”:“请说说final、finally和finalize的区别”。这三个词长得太像了,以至于很多初学者甚至工作一两年的开发者,都容易把它们搞混。

但话说回来,能把这三者彻底分清,恰恰是检验Ja va基础是否扎实的一块试金石。它们背后代表了Ja va语言设计的不同维度:一个是关于“不变性”的约束,一个是关于“可靠性”的保证,还有一个则是关于“生命周期”的历史遗留问题。

今天,我们就来把这三个“final家族”成员彻底拆解清楚。从定义、用法、底层机制到最佳实践,一次性讲透,并附上流程图和对比表格,帮你建立清晰的知识图谱。

2. 三者的核心区别速览

在深入细节之前,先通过下面这张表,快速抓住它们最核心的不同点。这张表值得你多看两眼,因为它基本概括了问题的全部要点。

关键字 所属范畴 主要作用 典型使用场景
final 关键字/修饰符 限制类、方法、变量不可变 常量定义、防止继承/重写
finally 异常处理块 无论是否异常,保证代码执行 释放资源(关闭文件、数据库连接等)
finalize Object 类方法 对象被垃圾回收前调用(已过时) 对象临终清理(不推荐)

3. final:不可变的守护者

首先登场的是final,它是一位“规则制定者”。作为Ja va语言中的一个修饰符,它的核心使命就是定义“不可变性”。一旦被它盯上,无论是类、方法还是变量,其特性就被“锁定”了。

3.1 final 修饰类

当一个类被声明为final时,就意味着它断绝了“子孙后代”。任何尝试继承它的行为都会在编译期被无情地阻止。这种设计通常用于那些功能完整、设计上不允许被修改或扩展的核心类。

最典型的例子就是String类。想象一下,如果String能被随意继承和修改,那Ja va世界的字符串安全将荡然无存。

public final class Constants {
    // 类体
}

// 编译错误:无法继承 final 类
// class MyConstants extends Constants { }

3.2 final 修饰方法

如果final用在方法上,那么这个方法就成了父类中的“祖传手艺”,子类只能继承和使用,但不能重写(Override)。这常用于保护那些关键的业务逻辑或算法实现,防止被子类意外篡改。

class Parent {
    public final void doSomething() {
        System.out.println("Parent action");
    }
}

class Child extends Parent {
    // 编译错误:无法重写 final 方法
    // public void doSomething() { }
}

3.3 final 修饰变量

这是final最常用也最容易让人困惑的地方。它修饰变量时,效果因变量类型而异:

  • 基本类型变量:值被锁定,成为真正的常量。
  • 引用类型变量:引用地址被锁定,但对象内部的状态(属性)依然可以改变。这就像你用胶水固定了一个盒子(引用),但盒子里的东西(对象内容)还是可以随意拿取更换。
  • 成员变量:必须在声明时、构造器中或初始化块里完成赋值,且仅此一次。
  • 局部变量:相对宽松,只要在使用前完成赋值即可,之后同样不可更改。
final int MAX_COUNT = 100;          // 常量
final List list = new ArrayList<>();
list.add("hello");                  // ✅ 允许(对象内容可变)
// list = new ArrayList<>();        // ❌ 编译错误,引用地址不可变

最佳实践:对于全局常量,通常结合static final一起使用,并且命名遵循全大写、下划线分隔的规范,比如MAX_THREAD_COUNT

4. finally:保证执行的最后防线

如果说final是关于空间的约束,那么finally就是关于时间的承诺。它是Ja va异常处理机制中的“定海神针”,与trycatch组成黄金搭档。

它的核心承诺是:无论try块里的代码是顺利执行,还是中途“翻车”抛出异常,finally块中的代码都一定会被执行(当然,前提是JVM没有退出或者线程没被强行中断)。

4.1 基本语法

try {
    // 可能抛出异常的代码
} catch (Exception e) {
    // 处理异常
} finally {
    // 一定会执行的代码,通常用于释放资源
    // 例如:关闭文件流、数据库连接、网络连接等
}

4.2 典型场景:资源释放

这是finally的“主场”。在Ja va 7之前,关闭资源的标准姿势就是在finally块里进行,确保资源在任何情况下都能被释放,避免内存泄漏。

FileInputStream fis = null;
try {
    fis = new FileInputStream("test.txt");
    // 读取文件...
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当然,现在更推荐使用Ja va 7引入的try-with-resources语法,它能自动关闭实现了AutoCloseable接口的资源,代码简洁又安全。但理解finally,依然是掌握异常处理和资源管理的基础。

4.3 特殊情况

即便是“最后防线”,也有几个特殊情况需要留意:

  • JVM退出:如果在trycatch里执行了System.exit(0),JVM直接退出,finally也就没机会执行了。
  • 异常覆盖:如果finally块里也抛出了异常,那么这个异常会“覆盖”掉trycatch里抛出的原始异常,这有时会让问题排查变得困难。

5. finalize:被遗忘的临终方法

最后这位finalize,可以说是Ja va家族里的“悲情角色”。它本是ja va.lang.Object类中定义的一个protected方法,设计初衷是好的:在垃圾回收器(GC)决定回收一个对象的内存之前,给它一个“临终遗言”的机会,用来释放一些本地资源(比如打开的文件句柄)。

5.1 基本用法(已过时)

public class MyResource {
    @Override
    protected void finalize() throws Throwable {
        try {
            // 释放本地资源(如关闭文件句柄)
            System.out.println("finalize called");
        } finally {
            super.finalize();
        }
    }
}

5.2 为什么 finalize 已被标记为废弃(deprecated)?

理想很丰满,现实很骨感。finalize()在实际应用中暴露出一系列致命问题,最终导致它在Ja va 9中被正式标记为deprecated(弃用)。下面这张表清晰地列出了它的“罪状”:

问题 说明
不确定性 GC 何时调用 finalize() 不可预知,甚至永远不会被调用(如果对象永远不被 GC)。
性能开销 重写 finalize() 会显著增加 GC 开销(对象需要两次标记)。
顺序问题 finalize() 执行顺序不受控制,可能导致资源访问冲突。
替代方案 使用 try-with-resourcesCleaner(Ja va 9+)或显式资源管理方法(如 close())。

所以,现在的行业共识非常明确:永远不要在新代码中重写finalize()方法

5.3 对象死亡流程(含 finalize)

深入理解Ja va中关键字final、finally、finalize的区别

从上面的流程图可以清晰地看到,一个对象从被判定为“可回收”到真正被GC回收,中间可能经历finalize()这个不确定的环节。更离谱的是,在finalize()方法中,对象甚至可以通过将自己重新赋值给某个静态变量来实现“复活”,这种反模式行为极其危险,也是它被抛弃的重要原因之一。

6. 综合对比与记忆口诀

6.1 对比表格

为了更直观地区分,我们把它们放在一起,从多个维度进行横向对比:

维度 final finally finalize
类型 关键字 关键字 方法名
所在包 语言核心 语言核心 ja va.lang.Object
主要作用 定义不可变性 异常后清理 对象临终处理(已过时)
可修饰目标 类、方法、变量 代码块 对象实例方法
是否推荐使用 ✅ 大量使用 ✅ 推荐 ❌ 强烈不推荐

6.2 记忆口诀

如果觉得概念太多记不住,可以试试这个口诀:final 定终(终态)身不二,finally 生死不离,finalize 来生难觅。

解释一下:

  • final 锁定类的继承、方法的重写、变量的改变,一旦确定,终身不变。
  • finally 无论程序是正常执行(生)还是抛出异常(死),它都会不离不弃地执行。
  • finalize 被垃圾回收调用,但时机飘忽不定,如今在Ja va新版本中更是难觅踪影。

7. 代码示例:三者的协作

理论讲完了,来看一个把它们放在一起的代码示例(再次强调,finalize仅为演示,实际项目请勿使用):

public class TestFinalFinallyFinalize {

    // final 常量
    private static final String MESSAGE = "Hello";

    public static void main(String[] args) {
        // final 局部变量
        final int times = 1;
        try {
            System.out.println(MESSAGE + " for " + times + " time");
            // 模拟异常
            // throw new RuntimeException();
        } finally {
            System.out.println("finally block: always executed");
        }
    }

    // 不推荐重写 finalize
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize called (deprecated)");
        super.finalize();
    }
}

程序正常运行的输出会是:

Hello for 1 time

finally block: always executed

至于finalize()方法,你大概率是看不到它被调用的,因为程序很快就结束了,对象可能根本没机会被垃圾回收。

8. 总结

好了,我们来做个最终盘点:

  • final:一个强大的修饰符,用于定义“不可变性”。用在类上禁止继承,用在方法上禁止重写,用在变量上定义常量。它是构建稳定、安全代码的基石。
  • finally:异常处理机制中的“安全卫士”。它保证了无论发生什么,关键的清理代码(如关闭资源)都能得到执行,是编写健壮程序的关键。
  • finalize:一个已被时代淘汰的“历史遗迹”。作为Object类的临终回调方法,因其不确定性、性能差等问题,在Ja va 9后被标记为废弃。现代Ja va开发中,请使用try-with-resourcesCleaner来管理资源。

彻底分清这三者,你的Ja va基础就算过了重要一关。最后给个面试小贴士:如果面试官问到finalize,除了讲清楚它的原理和问题,一定要主动提一句它已被废弃,并说明现代的替代方案。这一个小小的细节,就能体现出你对Ja va语言发展的跟进程度。

本文转载于:https://www.jb51.net/program/3646135jg.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注