1. 认识 finally 块
当你处理资源——文件、网络连接、数据库——时,必须确保它们总是被关闭或释放,即使在执行过程中发生了错误。为此,Java 提供了一个专门的代码块——finally。
finally 如何工作?
finally 块是 try-catch-finally 结构的一部分。finally 中的代码总会执行(如果编写了该块)——无论是否发生异常。即使在 try 中有 return 或抛出了异常——finally 仍然会执行(除非计算机被关闭,或者通过 System.exit(0) 强制终止了程序)。
语法:
try {
// 可能抛出异常的代码
} catch (ExceptionType e) {
// 错误处理
} finally {
// 这段代码总会执行!
}
示例
try {
System.out.println("开始运行");
int result = 10 / 0; // 这里会发生错误
System.out.println("结果:" + result);
} catch (ArithmeticException e) {
System.out.println("错误:被零除");
} finally {
System.out.println("无论如何这段代码都会执行");
}
运行结果:
开始运行
错误:被零除
无论如何这段代码都会执行
发生了什么?
- 在 try 块中,我们尝试将两个数相除并发生错误。
- 如果在除法时发生错误——catch 会处理它。
- 但是! 无论如何 finally 都会执行,并向控制台打印信息。
2. 没有 catch 的 finally
构造有 3 种变体:
- 完整形式:try-catch-finally
- 没有 finally:try-catch
- 没有 catch:try-finally
第三种变体用于由上层方法来捕获并处理错误的场景。但需要 finally 块来保证执行特定的代码:
- 关闭文件、网络连接、数据库。
- 释放任何资源(例如锁)。
- 日志记录:写入操作完成的信息。
示例:
try {
System.out.println("进行除法");
int result = 10 / 0; // 错误!
System.out.println("结果:" + result);
} finally {
System.out.println("finally 块已执行");
}
结果:
进行除法
finally 块已执行
Exception in thread "main" java.lang.ArithmeticException: / by zero
finally 何时不会执行?
几乎总会执行。例外情况包括:
- 使用 System.exit(0) 强制结束程序。
- 执行 finally 的线程被强制“杀死”。
- 计算机被关闭。
3. throw 语句:如何手动抛出异常
有时 Java 会自行“抛出”异常(例如,被零除、数组越界)。但也有一些情况需要由你明确指出:“这是错误!我无法继续执行!”。为此,Java 提供了 throw 语句。
类比:如果你在商店看到过期商品——你会“提出”投诉。代码中也是如此:如果不对劲——就抛出异常。
throw 的语法
throw new ExceptionType("错误信息");
ExceptionType —— 任何继承自 Throwable 的类(通常是 Exception 或 RuntimeException)。括号里的消息有助于理解哪里出了问题。
示例:方法参数校验
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为零");
}
return a / b;
}
用法:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("结果:" + result);
} catch (IllegalArgumentException e) {
System.out.println("错误:" + e.getMessage());
}
}
结果:
错误:除数不能为零
何时使用 throw?
- 方法参数校验(例如传入了 null 或无效数据)。
- 检查对象状态(例如试图从余额为 0 欧元的账户取钱)。
- 在 catch 中——如果想把异常“向上抛”(例如添加更多信息)。
4. 组合使用 try-catch-finally 与 throw
有时这些结构会一起配合使用。例如,你捕获到一个错误,但随后决定抛出一个更具信息量的新异常。
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("除数不能为零");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("字符串 '" + text + "' 不是一个数字");
} finally {
System.out.println("尝试处理字符串:" + text);
}
}
用法:
try {
int result = parseAndDivide("42a", 2);
System.out.println("结果:" + result);
} catch (IllegalArgumentException e) {
System.out.println("错误:" + e.getMessage());
}
结果:
尝试处理字符串:42a
错误:字符串 '42a' 不是一个数字
重要细节:return 与 finally
即使在 try 块中有 return,finally 仍然会执行!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally 仍然会执行!");
}
}
调用 getValue() 将输出:
finally 仍然会执行!
5. 使用 finally 和 throw 时的常见错误
错误 №1:没有使用 finally 关闭资源。
非常常见的问题:打开了文件却未关闭——导致资源泄漏。务必使用 finally(或稍后会提到的 try-with-resources)。
错误 №2:抛出了异常却没有处理。
如果你用 throw 抛出了异常,但没有在任何地方捕获它(没有 try-catch),程序会异常终止。始终考虑由谁来捕获你的异常。
错误 №3:在 finally 中 return。
如果你不小心在 finally 中写了 return,它会“覆盖”之前的 return 或 throw。这会导致非常隐蔽的 Bug。强烈不建议这样做!
public int tricky() {
try {
return 1;
} finally {
return 2; // 危险:返回的是 2,而不是 1!
}
}
结果: 将返回 2,尽管在 try 中是 1。
错误 №4:丢失异常信息。
如果你捕获了一个异常,然后抛出一个新的,但没有保留原始异常(e)的信息,你会丢失调用栈,从而增加调试难度。更好的写法是:
catch (NumberFormatException e) {
throw new IllegalArgumentException("转换错误", e);
}
GO TO FULL VERSION