杰瑞科技汇

java 异常 finally

finally 的核心作用

finally 关键字用于定义一个总是被执行的代码块,无论 try 块中是否发生异常,也无论是否有 catch 块捕获了该异常。

java 异常 finally-图1
(图片来源网络,侵删)

finally 块中的代码是“铁定要执行的”。

它的主要用途有两个:

  1. 资源清理:这是 finally 最经典和最重要的用途,关闭文件、关闭数据库连接、释放网络连接等,这些资源通常很宝贵,必须确保在使用后被释放,即使过程中发生了错误。
  2. 执行收尾操作:执行一些无论成功与否都需要完成的逻辑,比如记录日志、发送通知、更新状态等。

finally 的语法结构

finally 关键字必须与 try 语句块一起使用,它有两种常见的语法结构:

try-catch-finally

try {
    // 可能会抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
} finally {
    // 无论是否发生异常,都一定会执行的代码
}

try-finally

这种结构用于处理那些你不想捕获或无法处理,但仍然需要执行清理操作的异常。

java 异常 finally-图2
(图片来源网络,侵删)
try {
    // 可能会抛出异常的代码
} finally {
    // 无论是否发生异常,都一定会执行的代码
}

finally 的执行时机(重点)

理解 finally 代码块在各种情况下的执行顺序是掌握它的关键。

情况 1:try 块正常执行(没有异常)

try 块中的代码正常执行完毕,没有抛出任何异常时,程序会跳过所有 catch 块,直接执行 finally 块。

public class FinallyExample1 {
    public static void main(String[] args) {
        try {
            System.out.println("Try 块开始执行");
            int result = 10 / 2; // 正常计算
            System.out.println("计算结果: " + result);
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常: " + e.getMessage());
        } finally {
            System.out.println("Finally 块被执行 - 资源清理");
        }
        System.out.println("程序继续执行...");
    }
}

输出:

Try 块开始执行
计算结果: 5
Finally 块被执行 - 资源清理
程序继续执行...

情况 2:try 块发生异常,并被 catch 块捕获

try 块中发生异常,catch 块成功捕获了该异常时,程序会先执行对应的 catch 块,然后执行 finally 块。

java 异常 finally-图3
(图片来源网络,侵删)
public class FinallyExample2 {
    public static void main(String[] args) {
        try {
            System.out.println("Try 块开始执行");
            int result = 10 / 0; // 抛出 ArithmeticException
            System.out.println("这行代码不会被执行");
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常: " + e.getMessage());
        } finally {
            System.out.println("Finally 块被执行 - 资源清理");
        }
        System.out.println("程序继续执行...");
    }
}

输出:

Try 块开始执行
捕获到算术异常: / by zero
Finally 块被执行 - 资源清理
程序继续执行...

情况 3:try 块发生异常,但没有被 catch 块捕获

try 块中抛出的异常类型与任何一个 catch 块都不匹配,异常会向外传播,在传播出去之前,finally 块仍然会被执行。

public class FinallyExample3 {
    public static void main(String[] args) {
        try {
            System.out.println("Try 块开始执行");
            String str = null;
            int length = str.length(); // 抛出 NullPointerException
            System.out.println("这行代码不会被执行");
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常"); // 这个 catch 不会执行
        } finally {
            System.out.println("Finally 块被执行 - 资源清理");
        }
        // 异常在这里继续向上传播,导致程序终止
        System.out.println("这行代码不会被执行");
    }
}

输出:

Try 块开始执行
Finally 块被执行 - 资源清理
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
    at FinallyExample3.main(FinallyExample3.java:8)

注意:虽然 finally 执行了,但未捕获的异常会导致程序最终终止。


finally 中的 return 语句(最容易混淆的点)

这是一个非常经典且重要的面试考点,如果在 finally 块中有 return 语句,它会覆盖掉 trycatch 块中的 return

规则:

  1. finally 块中的代码会在 trycatch 块中的 return 语句之前执行。
  2. finally 块中包含了 return 语句,这个 return 将会成为整个方法最终的返回值,即使 trycatch 块中也有 return 语句

示例代码:

public class FinallyReturnExample {
    public static int testFinallyWithReturn() {
        int result = 10;
        try {
            System.out.println("Try 块执行,准备返回 result: " + result);
            return result; // 这行代码的执行被“暂停”,等待 finally 完成
        } catch (Exception e) {
            // ...
        } finally {
            System.out.println("Finally 块执行,修改 result 的值为 20");
            result = 20;
            System.out.println("Finally 块中,return result: " + result);
            return result; // 这个 return 会覆盖掉 try 中的 return
        }
        // return result; // 这行代码永远不会被执行到
    }
    public static void main(String[] args) {
        int finalResult = testFinallyWithReturn();
        System.out.println("方法最终返回的值是: " + finalResult);
    }
}

执行流程分析:

  1. try 块开始执行。
  2. try 块中的 return result; 准备返回值 10,但此时,JVM 会保留这个返回值,然后去执行 finally 块。
  3. finally 块执行,将 result 修改为 20
  4. finally 块遇到 return result;,它决定了方法的最终返回值是 20
  5. finally 块执行完毕。
  6. 方法将 finally 块中产生的返回值 20 返回给调用者。

输出:

Try 块执行,准备返回 result: 10
Finally 块执行,修改 result 的值为 20
Finally 块中,return result: 20
方法最终返回的值是: 20

强烈建议在 finally 块中避免使用 return 语句,因为它会隐藏掉 trycatch 块中的原始返回逻辑,导致代码行为难以预测。


finallytry-with-resources (Java 7+)

为了解决传统 finally 在资源管理上可能出现的代码冗长和容易出错的问题(比如忘记关闭资源,或者在关闭资源时又抛出新的异常),Java 7 引入了 try-with-resources 语句。

这是现代 Java 中推荐使用的资源管理方式。

传统 finally 方式(不推荐):

FileInputStream fis = null;
try {
    fis = new FileInputStream("test.txt");
    // 读取文件...
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close(); // 关闭资源,但这里也可能抛出 IOException
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try-with-resources 方式(推荐):

任何实现了 AutoCloseable 接口(所有 Closeable 类都实现了它)的资源都可以在 try 语句中声明。try 块执行完毕后(无论是否发生异常),这些资源都会被自动关闭,其顺序与声明顺序相反。

// try() 中声明的资源会在 try 块结束后自动关闭
try (FileInputStream fis = new FileInputStream("test.txt")) {
    // 读取文件...
    // 如果这里发生异常,fis 依然会被正确关闭
} catch (IOException e) {
    e.printStackTrace();
} // fis.close() 会被自动调用,无需在 finally 中手动操作

优点

  • 代码更简洁:消除了冗余的 finally 块。
  • 更安全:自动关闭资源,不会忘记。
  • 异常处理更清晰:如果资源关闭时发生异常,它会被“抑制”(suppressed),并与主异常一起关联,而不是覆盖主异常。

总结与最佳实践

特性 描述
核心作用 确保一段代码无论如何都会被执行,主要用于资源清理。
执行时机 trycatch之后return 语句之前执行。
finally 中的 return 会覆盖 trycatch 块中的 return,应极力避免
资源管理 传统方式:在 finally 中调用 close()现代方式:使用 try-with-resources强烈推荐
不执行的情况 tryfinally 块中执行了 System.exit() 来终止 JVM,或者线程在 tryfinally 块中被中断。

最佳实践:

  1. 优先使用 try-with-resources:对于所有实现了 AutoCloseable 的资源(如文件流、数据库连接、Socket 等),都应使用 try-with-resources
  2. finally 用于非资源清理:对于那些无法使用 try-with-resources 的资源或必须执行的收尾逻辑(如日志记录),才使用 finally 块。
  3. 避免在 finally 中使用 return:保持代码逻辑的清晰和可预测性。
  4. 不要在 finally 中抛出未捕获的异常:如果在 try 块中抛出了异常 A,在 finally 块中又抛出了异常 B,那么异常 A 将会被“丢弃”,只有异常 B 会被抛出给上层调用者,这会掩盖掉原始问题,如果必须在 finally 中处理可能出错的代码,请务必使用 try-catch 将其捕获。
分享:
扫描分享到社交APP
上一篇
下一篇