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

finally 块中的代码是“铁定要执行的”。
它的主要用途有两个:
- 资源清理:这是
finally最经典和最重要的用途,关闭文件、关闭数据库连接、释放网络连接等,这些资源通常很宝贵,必须确保在使用后被释放,即使过程中发生了错误。 - 执行收尾操作:执行一些无论成功与否都需要完成的逻辑,比如记录日志、发送通知、更新状态等。
finally 的语法结构
finally 关键字必须与 try 语句块一起使用,它有两种常见的语法结构:
try-catch-finally
try {
// 可能会抛出异常的代码
} catch (ExceptionType1 e1) {
// 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
// 处理 ExceptionType2 类型的异常
} finally {
// 无论是否发生异常,都一定会执行的代码
}
try-finally
这种结构用于处理那些你不想捕获或无法处理,但仍然需要执行清理操作的异常。

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 块。

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 语句,它会覆盖掉 try 或 catch 块中的 return。
规则:
finally块中的代码会在try或catch块中的return语句之前执行。finally块中包含了return语句,这个return将会成为整个方法最终的返回值,即使try或catch块中也有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);
}
}
执行流程分析:
try块开始执行。try块中的return result;准备返回值10,但此时,JVM 会保留这个返回值,然后去执行finally块。finally块执行,将result修改为20。finally块遇到return result;,它决定了方法的最终返回值是20。finally块执行完毕。- 方法将
finally块中产生的返回值20返回给调用者。
输出:
Try 块执行,准备返回 result: 10
Finally 块执行,修改 result 的值为 20
Finally 块中,return result: 20
方法最终返回的值是: 20
强烈建议在 finally 块中避免使用 return 语句,因为它会隐藏掉 try 或 catch 块中的原始返回逻辑,导致代码行为难以预测。
finally 与 try-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),并与主异常一起关联,而不是覆盖主异常。
总结与最佳实践
| 特性 | 描述 |
|---|---|
| 核心作用 | 确保一段代码无论如何都会被执行,主要用于资源清理。 |
| 执行时机 | 在 try 或 catch 块之后,return 语句之前执行。 |
finally 中的 return |
会覆盖 try 或 catch 块中的 return,应极力避免。 |
| 资源管理 | 传统方式:在 finally 中调用 close()。现代方式:使用 try-with-resources,强烈推荐。 |
| 不执行的情况 | 在 try 或 finally 块中执行了 System.exit() 来终止 JVM,或者线程在 try 或 finally 块中被中断。 |
最佳实践:
- 优先使用
try-with-resources:对于所有实现了AutoCloseable的资源(如文件流、数据库连接、Socket 等),都应使用try-with-resources。 finally用于非资源清理:对于那些无法使用try-with-resources的资源或必须执行的收尾逻辑(如日志记录),才使用finally块。- 避免在
finally中使用return:保持代码逻辑的清晰和可预测性。 - 不要在
finally中抛出未捕获的异常:如果在try块中抛出了异常 A,在finally块中又抛出了异常 B,那么异常 A 将会被“丢弃”,只有异常 B 会被抛出给上层调用者,这会掩盖掉原始问题,如果必须在finally中处理可能出错的代码,请务必使用try-catch将其捕获。
