finally 块是什么?
finally 是 Java 异常处理机制中的一个关键字,它与 try 和 catch 块一起使用。finally 块的主要特点是:无论 try 块中是否发生异常,也无论异常是否被 catch 块捕获,finally 块中的代码都一定会被执行。

它的基本语法结构如下:
try {
// 可能会抛出异常的代码
} catch (ExceptionType1 e1) {
// 捕获并处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
// 捕获并处理 ExceptionType2 类型的异常
} finally {
// 无论是否发生异常,这里的代码都会被执行
// 通常用于资源清理,如关闭文件、数据库连接等
}
finally 块的主要用途
finally 块最主要、最经典的用途就是资源清理。
在很多情况下,程序会占用一些外部资源,
- 文件(
FileInputStream,FileOutputStream) - 网络连接(
Socket) - 数据库连接(
Connection,Statement)
这些资源是有限的,如果不及时释放,会导致资源泄露,最终可能导致程序崩溃。

示例:不使用 finally 的风险
import java.io.FileInputStream;
import java.io.IOException;
public class WithoutFinally {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 读取文件...
int data = fis.read();
if (data == -1) { // 假设读到文件末尾
System.out.println("文件读取完毕");
return; // 如果在这里 return,fis 就不会被关闭!
}
} catch (IOException e) {
e.printStackTrace();
}
// try 块中没有异常,并且没有执行 return,这里才会执行
if (fis != null) {
try {
fis.close(); // 手动关闭,但很容易被遗忘或跳过
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在上面的代码中,read() 方法返回 -1,程序会执行 return 语句,从而跳过后面的 fis.close(),导致文件句柄泄露。
使用 finally 解决问题
import java.io.FileInputStream;
import java.io.IOException;
public class WithFinally {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 读取文件...
int data = fis.read();
if (data == -1) {
System.out.println("文件读取完毕");
return; // 即使在这里 return,finally 块也会执行
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 这是清理资源的最佳位置,无论何种情况都会执行
if (fis != null) {
try {
fis.close();
System.out.println("文件已成功关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用 finally 后,无论 try 块是正常结束、因为异常中断,还是因为 return 语句退出,fis.close() 都会被保证执行,从而避免了资源泄露。
finally 块的执行规则(核心与难点)
finally 块的执行遵循一些非常重要的规则,理解这些规则对于写出健壮的代码至关重要。
规则 1:正常情况
try块中的代码正常执行完毕,没有抛出任何异常。- 然后执行
catch块(如果有的话)。 - 执行
finally块。
public class NormalExecution {
public static void main(String[] args) {
try {
System.out.println("在 try 块中");
int a = 10 / 2;
System.out.println("a = " + a);
} catch (Exception e) {
System.out.println("在 catch 块中");
} finally {
System.out.println("在 finally 块中");
}
System.out.println("程序结束");
}
}
// 输出:
// 在 try 块中
// a = 5
// 在 finally 块中
// 程序结束
规则 2:try 块抛出异常并被 catch 捕获
try块中的代码抛出异常。- 程序立即跳转到匹配的
catch块执行。 catch块执行完毕后,执行finally块。
public class ExceptionCaught {
public static void main(String[] args) {
try {
System.out.println("在 try 块中");
int a = 10 / 0; // 抛出 ArithmeticException
System.out.println("这行代码不会被执行");
} catch (ArithmeticException e) {
System.out.println("在 catch 块中,捕获到异常: " + e.getMessage());
} finally {
System.out.println("在 finally 块中");
}
System.out.println("程序结束");
}
}
// 输出:
// 在 try 块中
// 在 catch 块中,捕获到异常: / by zero
// 在 finally 块中
// 程序结束
规则 3:try 块抛出异常但未被 catch 捕获
try块中的代码抛出异常,但没有catch块能捕获它。- 程序会先执行
finally块。 finally块执行完毕后,异常会继续向外层(调用该方法的方法)传播。
public class ExceptionNotCaught {
public static void main(String[] args) {
try {
System.out.println("在 try 块中");
String s = null;
System.out.println(s.length()); // 抛出 NullPointerException
} catch (ArithmeticException e) { // catch 块不匹配
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 "s" is null
// at ExceptionNotCaught.main(ExceptionNotCaught.java:8)
注意:System.out.println("这行代码不会被执行"); 没有被打印,因为异常在 finally 执行后继续抛出,导致 main 方法中断。
规则 4:try 或 catch 块中有 return 语句
这是最关键也最容易出错的一点:finally 块的执行优先级高于 return 语句。
- 当
try或catch块执行到return语句时,它不会立即返回。 - 它会先去检查是否存在
finally块。 - 如果存在,它会先执行
finally块中的所有代码。 - 只有在
finally块执行完毕后,才会执行try或catch块中的return语句,真正地返回值并退出方法。
public class FinallyWithReturn {
public static int test() {
int result = 10;
try {
System.out.println("try 块开始");
return result; // 准备返回 10
} finally {
System.out.println("finally 块开始");
result = 20; // 修改了 result 的值
System.out.println("finally 块结束");
// 注意:这里如果写 return result;,会覆盖 try 块中的 return
}
}
public static void main(String[] args) {
int value = test();
System.out.println("方法返回的值是: " + value);
}
}
// 输出:
// try 块开始
// finally 块开始
// finally 块结束
// 方法返回的值是: 10
解释:
try 块中的 return result; 在字节码层面,会先把返回值 10 压入一个“返回值栈”中,程序会去执行 finally 块。finally 块执行完毕后,方法会从“返回值栈”中取出之前压入的值 10,并将其返回。finally 块中对 result 的修改不会影响已经压入栈的返回值。
另一个例子:finally 块中的 return
finally 块中也包含了 return 语句,那么它会覆盖 try 和 catch 块中的 return。
public class FinallyWithReturnOverride {
public static int test() {
try {
System.out.println("try 块开始");
return 10;
} finally {
System.out.println("finally 块开始");
return 30; // 这个 return 会覆盖 try 块中的 return
}
}
public static void main(String[] args) {
int value = test();
System.out.println("方法返回的值是: " + value);
}
}
// 输出:
// try 块开始
// finally 块开始
// 方法返回的值是: 30
在 finally 块中使用 return 是一个非常糟糕的编程实践,因为它会隐藏原始的异常和返回值,使代码逻辑变得混乱且难以调试。
规则 5:try 或 catch 块中抛出异常
try或catch块中抛出了一个异常(没有被内部捕获),程序会先去执行finally块。finally块中没有抛出新的异常,那么原始的异常会继续向外层传播。finally块中也抛出了一个异常,那么原始的异常会被丢弃,finally块中抛出的新异常会向外层传播。
public class FinallyWithException {
public static void test() {
try {
System.out.println("try 块开始");
throw new RuntimeException("来自 try 块的异常");
} catch (Exception e) {
System.out.println("catch 块开始");
throw new RuntimeException("来自 catch 块的异常");
} finally {
System.out.println("finally 块开始");
throw new RuntimeException("来自 finally 块的异常"); // 这会覆盖之前的所有异常
}
}
public static void main(String[] args) {
try {
test();
} catch (Exception e) {
System.out.println("捕获到最终异常: " + e.getMessage());
}
}
}
// 输出:
// try 块开始
// catch 块开始
// finally 块开始
// 捕获到最终异常: 来自 finally 块的异常
在 finally 块中抛出异常同样是极其危险的,它会掩盖掉真正导致问题的原始异常。
Java 7+ 的 try-with-resources 语句
为了更优雅、更安全地处理资源,Java 7 引入了 try-with-resources 语句,它能够自动关闭实现了 AutoCloseable 接口(或 Closeable 接口)的资源,从而替代了手动 finally 块的写法。
语法:
try (ResourceType resource = new ResourceType()) {
// 使用资源
} // 资源在这里会自动关闭,无论是否发生异常
示例:
import java.io.FileInputStream;
import java.io.IOException;
public class TryWithResources {
public static void main(String[] args) {
// try-with-resources 会自动调用 fis.close()
try (FileInputStream fis = new FileInputStream("test.txt")) {
// 读取文件...
int data = fis.read();
System.out.println("读取到数据: " + data);
} catch (IOException e) {
e.printStackTrace();
}
// fis 已经被自动关闭了,无需手动 finally
}
}
优点:
- 代码更简洁:消除了冗余的
finally块。 - 更安全:资源一定会被关闭,即使发生异常或
return语句。 - 可以同时管理多个资源:用分号隔开即可。
try (FileInputStream fis = new FileInputStream("in.txt"); FileOutputStream fos = new FileOutputStream("out.txt")) { // ... } catch (IOException e) { // ... }
| 特性/规则 | 描述 | 示例/建议 |
|---|---|---|
| 核心作用 | 保证代码一定会被执行,主要用于资源清理。 | finally { resource.close(); } |
| 执行顺序 | 优先级高于 return 和异常传播。 |
try { return; } finally { ... } 会先执行 finally 再返回。 |
与 return |
finally 中的 return 会覆盖 try/catch 中的 return。 |
严禁在 finally 中使用 return。 |
| 与异常 | finally 中抛出的异常会覆盖 try/catch 中抛出的异常。 |
严禁在 finally 中抛出新的异常或吞没异常。 |
| 现代实践 | 优先使用 try-with-resources 来管理实现了 AutoCloseable 的资源。 |
try (MyResource r = ...) { ... } |
一句话总结:finally 是一个强大的工具,用于确保关键清理逻辑的执行,它的强大也伴随着风险,尤其是在与 return 和异常交互时,在现代 Java 开发中,对于资源管理,应优先考虑使用 try-with-resources。
