Java异常finally块:无论是否异常,这段代码总会执行

我们在使用Java处理异常的时候,经常会用到try、catch和finally这三个关键词。其中,try块用来包裹可能会出现异常的代码,catch块用来捕获和处理try块中发生的异常。而今天我们要重点说的finally块,它有个特别的特点——无论try块有没有异常发生,无论异常有没有被catch到,finally块里的代码都会执行。这听起来是不是很有用?

一、基本语法结构

首先,我们先回忆一下try-catch-finally的基本结构:

try {
    // 可能出现异常的代码
} catch (异常类型 e) {
    // 捕获并处理异常
} finally {
    // 无论是否异常,都会执行的代码
}

在这个结构中,finally块是可选的,但它的执行逻辑是固定的:只要try块被执行(哪怕try里只有一行代码),不管后续有没有异常或者异常是否被捕获,finally块的代码都会“最后执行”。

二、不同场景下的finally执行情况

我们通过几个例子来看看finally块到底是怎么执行的。

1. try块正常执行(无异常)

当try块里的代码没有异常时,catch块不会执行,但finally块依然会执行:

public class FinallyDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("try块:这行代码会正常执行");
        } catch (Exception e) {
            System.out.println("catch块:这里不会执行(因为没有异常)");
        } finally {
            System.out.println("finally块:无论是否异常,我都会执行!");
        }
    }
}

输出结果

try块:这行代码会正常执行
finally块:无论是否异常,我都会执行!

2. try块有异常,且被catch捕获

当try块里的代码抛出异常时,catch块会处理异常,之后finally块依然会执行:

public class FinallyDemo2 {
    public static void main(String[] args) {
        try {
            int a = 10 / 0; // 这行会抛出ArithmeticException(除零异常)
            System.out.println("try块:这行代码不会执行(异常已经发生)");
        } catch (ArithmeticException e) {
            System.out.println("catch块:捕获到异常:" + e.getMessage());
        } finally {
            System.out.println("finally块:我被执行了,异常已被处理!");
        }
    }
}

输出结果

catch块:捕获到异常:/ by zero
finally块:我被执行了,异常已被处理!

3. try块有异常,但没有catch块

即使没有catch块,只要try块被执行,finally块依然会执行,然后异常会继续向上传播(导致程序终止):

public class FinallyDemo3 {
    public static void main(String[] args) {
        try {
            int b = 20 / 0; // 抛出ArithmeticException(除零异常)
        } finally {
            System.out.println("finally块:我一定会执行!");
        }
        // 这里不会执行,因为异常会向上传播
    }
}

输出结果

finally块:我一定会执行!
Exception in thread "main" java.lang.ArithmeticException: / by zero

注意:即使没有catch块,finally块依然会执行,只是异常会继续抛出到上层代码,导致程序终止。

三、为什么需要finally块?最常见的用途——资源释放

你可能会问:“既然finally块这么重要,那它到底用来干嘛呢?”最典型的场景是释放资源,比如:
- 关闭文件、数据库连接、网络连接;
- 释放内存、线程锁等。

这些资源如果不主动关闭,会导致系统资源泄漏(比如文件一直占用,无法被其他程序访问)。而finally块能确保无论try块是否出错,资源释放的代码都会执行。

举个文件关闭的例子:

import java.io.FileReader;
import java.io.IOException;

public class FileResourceExample {
    public static void readFile() {
        FileReader fr = null; // 声明文件流,初始化为null
        try {
            fr = new FileReader("test.txt"); // 打开文件(可能出错)
            int data = fr.read(); // 读取文件数据(可能出错)
            System.out.println("读取到的数据:" + data);
        } catch (IOException e) {
            System.out.println("文件操作出错:" + e.getMessage());
        } finally {
            // 确保文件流被关闭,无论是否出错
            if (fr != null) {
                try {
                    fr.close(); // 关闭文件流
                    System.out.println("文件流已关闭");
                } catch (IOException e) {
                    e.printStackTrace(); // 关闭时出错也处理一下
                }
            }
        }
    }
}

这里,无论文件读取是否出错,fr.close()都会在finally块中执行,确保文件流被关闭,避免资源浪费。

四、注意:finally块可能“覆盖”return值

如果try块中有return语句,finally块的return会“覆盖”try块的return值(但这种情况不推荐,容易引发歧义):

public class FinallyReturnExample {
    public static int test() {
        try {
            return 1; // 先假设返回1
        } finally {
            return 2; // 但finally块的return会覆盖上面的return
        }
    }

    public static void main(String[] args) {
        System.out.println(test()); // 输出结果是2
    }
}

输出结果

2

这是因为finally块的执行优先级最高,会在try块的return之后执行,并直接返回自己的return值。

五、总结:finally块的核心特点

  1. 必执行性:只要try块被执行(哪怕只有一行代码),finally块就会执行,与是否捕获异常无关。
  2. 资源释放:常用于关闭文件、数据库连接等资源,避免资源泄漏。
  3. 执行优先级:如果try和finally都有return,finally的return会覆盖try的return。

掌握finally块的用法,能让你的Java代码更健壮——即使出现异常,也能确保关键的收尾操作(比如关闭资源)不会被遗漏。这是Java异常处理中非常重要的一环!

小夜