final、finally、finalize 别再搞混!从基础到实战避坑,一篇讲透

内容分享4天前发布
0 3 0

final、finally、finalize 别再搞混!从基础到实战避坑,一篇讲透

作为 Java 开发者,你是不是也曾经对着 “final、finally、finalize” 这三个 “长得像” 的词犯迷糊?明明拼写只差几个字母,作用却天差地别 —— 一个管 “不可变”,一个管 “资源清理”,一个早就被 Java 官方 “打入冷宫”。

今天这篇文章,用表格对比、代码实例、避坑指南帮你彻底分清,看完直接能应对面试和开发!


一、1 张表格秒懂核心区别

先上 “干货总结表”,把三者最关键的差异摆出来,看完心里先有谱:

特性

final(不可变修饰符)

finally(异常处理块)

finalize(对象终结方法)

本质

关键字(修饰类 / 方法 / 变量)

关键字(try-catch 配套块)

Object 类的方法(JDK9 + 弃用)

核心作用

保证 “不可变”,防篡改

确保资源必清理(无论异常)

垃圾回收前 “最后清理”

执行时机

编译期检查 + 运行时生效

try/catch 执行后必触发(特例除外)

垃圾回收时随机执行(不确定)

可控性

开发者完全掌控

开发者完全掌控

完全依赖 JVM(不可控)

生产推荐度

✅ 强烈推荐(安全 + 线程安全)

✅ 推荐(现代用 try-with-resources 替代)

❌ 绝对不推荐(性能差 + 不稳定)


二、逐个拆解:用法 + 案例 + 避坑

1. final:Java 里的 “不可变锁”

final 是用来 “上锁” 的修饰符,修饰不同对象,锁的效果也不同 —— 但记住:锁的是 “引用” 或 “继承关系”,不是 “对象内容”(这是高频坑!)。

(1)修饰类:禁止继承,像 “密封的盒子”

被 final 修饰的类,不能被任何子类继承,列如 JDK 里的String类就是 final 的,防止被篡改核心逻辑。

// ✅ 正确:String是final类,不能被继承
final class String { 
    // ... 核心逻辑
}

// ❌ 错误:编译报错,不能继承final类
class MyString extends String { 
}

用场景:核心工具类(如java.lang.Math)、安全敏感类,防止子类重写破坏逻辑。

(2)修饰方法:禁止重写,像 “固定的流程”

被 final 修饰的方法,子类不能重写,保证方法行为 “一成不变”。

class Parent {
    // final方法:子类不能改
    public final void payBill() {
        System.out.println("固定支付流程:校验→扣款→对账");
    }
}

class Child extends Parent {
    // ❌ 错误:编译报错,不能重写final方法
    @Override
    public void payBill() {
        System.out.println("想改支付流程?不行!");
    }
}

用场景:模板方法模式中的 “核心步骤”,列如支付、登录的固定流程。

(3)修饰变量:禁止改引用,像 “钉死的指针”

这是最容易踩坑的点!final 修饰变量分两种情况:

  • 基本类型(int、String 等):值完全不能改;
  • 引用类型(List、Map 等):引用不能改,但对象里的内容能改!
// 案例1:final修饰基本类型(值不能改)
final int age = 20;
age = 21; // ❌ 编译报错:不能改值

// 案例2:final修饰引用类型(引用不能改,但内容能改)
final List<String> list = new ArrayList<>();
list.add("Java"); // ✅ 正确:内容能加
list.add("Python"); // ✅ 正确:内容能加
list = new ArrayList<>(); // ❌ 错误:引用不能改

避坑提醒:想让引用类型 “完全不可变”?得用
Collections.unmodifiableList(),列如:

final List<String> unmodList = Collections.unmodifiableList(list);
unmodList.add("C++"); // ❌ 运行报错:不能改内容

2. finally:资源的 “最后守护者”

finally 是 try-catch 的 “配套工具”,作用只有一个:无论 try 里是否抛异常、是否 return,finally 里的代码必执行(除了 JVM 退出的特例),专门用来清理资源(列如关文件、关数据库连接)。

(1)传统用法:手动关资源

以前没有 try-with-resources 时,靠 finally 保证资源必关:

public void readFile(String path) {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(path);
        // 读文件逻辑
    } catch (IOException e) {
        // 处理异常
    } finally {
        // 无论是否异常,必关流
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                // 处理关流异常
            }
        }
    }
}

(2)现代用法:try-with-resources(推荐!)

Java 7 + 推出的 “自动资源管理”,替代了繁琐的 finally,资源会自动关闭(只要类实现AutoCloseable接口):

public void readFileModern(String path) {
    // 资源在try括号里声明,结束后自动关
    try (FileInputStream fis = new FileInputStream(path);
         BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // 处理异常
    }
    // 不用写finally,fis和br自动关!
}

(3)必记特例:这些情况 finally 不执行

只有一种情况会让 finally “失效”—— 调用System.exit(0)(强制退出 JVM):

try {
    System.out.println("try执行");
    System.exit(0); // 强制退出JVM
} finally {
    System.out.println("finally执行"); // ❌ 看不到:JVM已经退了
}

(4)高频坑:finally 里别写 return

如果 finally 里写了 return,会 “覆盖” try 里的 return 值,而且很难排查:

public int getNum() {
    try {
        return 1; // 这个值会被覆盖
    } finally {
        return 2; // ❌ 不推荐!实际返回2
    }
}

避坑提醒:finally 只用来 “清理资源”,绝对不要写 return、抛异常!

3. finalize:被 Java 官方 “放弃” 的方法

finalize 是 Object 类的一个方法,原本设计是:对象被垃圾回收(GC)前,JVM 会调用它做 “最后清理”(列如释放 native 资源)。但目前已经被 JDK 9 标记为@Deprecated(弃用),缘由很简单:太不靠谱!

(1)为什么不推荐?3 个致命问题

  • 执行时机不确定:GC 什么时候跑、会不会调用 finalize,完全由 JVM 决定,可能对象都被回收了,finalize 还没执行;
  • 性能极差:会拖慢 GC 速度,甚至可能导致内存泄漏;
  • 异常会被吞:finalize 里抛的异常,JVM 会直接忽略,排查问题时找不到痕迹。

(2)替代方案:用这些更靠谱的方式

  • 显式 close 方法:列如InputStream.close(),主动调用清理;
  • try-with-resources:自动管理资源(前面讲过,最推荐);
  • Cleaner 机制:Java 9 + 提供的替代 finalize 的工具,但一般开发用不到(底层框架可能会用)。

(3)反面案例:别再写这种代码!

public class BadFinalize {
    @Override
    protected void finalize() throws Throwable {
        // ❌ 不推荐:依赖finalize清理资源
        System.out.println("清理资源");
        super.finalize();
    }
}

三、实战避坑指南:3 个必记原则

  1. final 用在 “需要不变” 的场景:成员变量用 final,减少并发问题(final 变量初始化后线程安全);工具类用 final,防止被继承篡改(列如自己写的加密工具类)。
  2. finally 只做 “资源清理”:别在 finally 里写业务逻辑、return、抛异常;能用电 try-with-resources,就别手动写 finally 关资源。
  3. finalize 绝对别碰:新项目里看到 finalize,直接重构;清理资源优先用 try-with-resources 或显式 close。

四、面试高频问题:3 个经典问答

Q1:final 修饰的类,真的完全不可变吗?

A:不必定。如果类里有引用类型成员变量,即使类是 final 的,成员变量的内容还是能改(除非成员变量也用 final + 不可变容器,列如
Collections.unmodifiableList())。

Q2:try-with-resources 能同时管理多个资源吗?

A:可以。多个资源用分号隔开,JVM 会按 “声明逆序” 自动关闭(列如先关BufferedReader,再关FileInputStream)。

Q3:为什么 Java 要弃用 finalize?

A:由于 finalize 执行时机不确定、性能差、异常会被吞,无法可靠地清理资源,所以官方推荐用 try-with-resources 等更可控的方式替代。

看完这篇,是不是彻底分清 final、finally、finalize 了?记住:final 管 “不可变”,finally 管 “资源清”,finalize 早 “被放弃”—— 下次面试或开发遇到,再也不会混淆啦!

#java##final##面试##finally#

© 版权声明

相关文章

3 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    灰烬aa 投稿者

    真不戳💪

    无记录
  • 头像
    FUArea 投稿者

    受益匪浅👏

    无记录
  • 头像
    逗比的雀大巢 投稿者

    final 是「常量/终态」——一次声明终身不变;finally 是「扫尾」——无论抛不抛异常都执行;finalize 是「遗嘱」——对象被回收前给它的最后一次自赎机会。#java#

    无记录