异常处理(try-catch-finally、throws、try-with-resources)
概述
在了解 异常体系 之后,需要掌握如何正确处理异常。Java 提供了三种核心机制:try-catch-finally 在方法内部捕获并处理异常;throws 在方法签名上声明「可能抛出的异常」,由调用方处理;try-with-resources(JDK 7+)用于自动关闭实现了 AutoCloseable 的资源(如流、连接),避免遗漏关闭导致资源泄漏。本文逐一说明语法、适用场景与组合用法。
try-catch-finally:捕获并处理异常
基本语法
在可能抛出异常的代码外包裹 try,用 catch 捕获指定类型的异常并处理,可选 finally 在无论是否发生异常时都执行(常用于释放资源)。
try {
// 可能抛出异常的代码
} catch (异常类型1 e) {
// 处理异常类型1
} catch (异常类型2 e) {
// 处理异常类型2
} finally {
// 可选:总会执行的代码(如关闭流)
}- try:必须,包裹可能抛出异常的代码。
- catch:可有一个或多个,按书写顺序匹配;捕获的异常类型可以是具体子类或父类(如先
catch (IOException e)再catch (Exception e)会编译报错,因为 Exception 已包含 IOException,应子类在前)。 - finally:可选;无论是否发生异常、是否被 catch,都会执行(除非 JVM 退出或线程被 kill)。
提示
从 JDK 7 开始支持多异常合并捕获:catch (IOException | SQLException e),同一 catch 块中 e 被视为这些类型的共同父类型,不能再用 e 调用各自特有的方法。
示例 1:基础 try-catch
public class TryCatchDemo {
public static void main(String[] args) {
try {
int n = Integer.parseInt("abc"); // 抛出 NumberFormatException
System.out.println(n);
} catch (NumberFormatException e) {
System.err.println("数字格式错误: " + e.getMessage());
}
System.out.println("程序继续执行");
}
}NumberFormatException 是非受检异常,不强制捕获;这里主动捕获后打印信息并继续运行。
示例 2:多 catch 与 finally
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public void readAndParse(String path) {
try {
String content = Files.readString(Path.of(path)); // 可能抛出 IOException
int value = Integer.parseInt(content.trim()); // 可能抛出 NumberFormatException
System.out.println("解析结果: " + value);
} catch (IOException e) {
System.err.println("文件读取失败: " + e.getMessage());
} catch (NumberFormatException e) {
System.err.println("内容不是有效数字: " + e.getMessage());
} finally {
// 例如:释放不实现 AutoCloseable 的某种资源,或打日志
System.out.println("readAndParse 结束");
}
}IOException 是受检异常,必须被捕获或通过 throws 声明;finally 无论是否发生异常都会执行。
示例 3:多异常合并捕获(JDK 7+)
try {
// 可能抛出 IOException 或 UnsupportedEncodingException 等
byte[] bytes = "hello".getBytes("UTF-8");
// ...
} catch (IOException e) {
// e 为 IOException 类型,可统一处理
System.err.println("IO 异常: " + e.getMessage());
}throws:声明抛出异常
基本语法
若当前方法不打算处理某种异常,可在方法签名上用 throws 声明,将异常交给调用方处理。调用方同样要么 try-catch,要么继续 throws,直到有调用方捕获或传到 main(再往外则交给 JVM,程序可能终止)。
修饰符 返回类型 方法名(参数列表) throws 异常类型1, 异常类型2 {
// 方法体
}注意
只有受检异常必须在方法上声明或在本方法内捕获;非受检异常(RuntimeException 及其子类)可声明也可不声明,编译器不强制。
示例 1:受检异常声明抛出
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
// 调用方必须处理 IOException(try-catch 或继续 throws)
public String readFirstLine(String path) throws IOException {
return Files.readAllLines(Path.of(path)).get(0);
}
// 调用方选择在此处捕获
public void useReadFirstLine(String path) {
try {
String line = readFirstLine(path);
System.out.println(line);
} catch (IOException e) {
System.err.println("读取失败: " + e.getMessage());
}
}示例 2:同时声明多个异常
import java.io.IOException;
import java.sql.SQLException;
public void saveToFileAndDb(String data) throws IOException, SQLException {
Files.writeString(Path.of("out.txt"), data); // 可能抛出 IOException
// 假设某方法可能抛出 SQLException
// jdbcTemplate.update(...);
}调用 saveToFileAndDb 的代码必须处理这两种受检异常(或其一,或继续 throws)。
try-with-resources:自动关闭资源
基本语法与适用场景
实现了 AutoCloseable(或 JDK 7 之前的 Closeable)的资源,如 InputStream、OutputStream、Reader、Writer、Connection 等,应在使用完毕后调用 close()。try-with-resources 会在 try 块正常结束或发生异常时自动调用 close(),避免遗忘关闭导致资源泄漏。
try (资源声明1; 资源声明2; ...) {
// 使用资源
}
// 离开 try 块时自动按声明顺序的逆序关闭资源- 资源声明形式:
ResourceType var = new ...,且ResourceType必须实现AutoCloseable。 - 可声明多个资源,用分号分隔;关闭顺序与声明顺序相反。
- JDK 9+ 可在 try 外先声明变量,再在 try 中引用(变量需为 effectively final)。
提示
优先使用 try-with-resources 管理所有「打开后需关闭」的资源,代码更简洁,且异常时也能保证关闭,避免 finally 中再写 close 的繁琐与遗漏。
示例 1:单资源
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public void readLines(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// br.close() 已自动调用,即使发生异常也会先关闭再抛出
}BufferedReader 实现了 Closeable(继承自 AutoCloseable),因此可放在 try-with-resources 中。
示例 2:多资源与异常抑制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public void copyFile(String src, String dest) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) {
out.write(buf, 0, n);
}
}
// 先关闭 out,再关闭 in;若关闭时抛出异常,会作为「抑制异常」附加到主异常
}若 try 块内抛出异常,关闭时再抛异常,则主异常会被保留,关闭产生的异常通过 Throwable.getSuppressed() 获取,不会丢失。
示例 3:在 try-with-resources 中捕获异常
public void readFirstLineSafe(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
System.out.println(br.readLine());
} catch (IOException e) {
System.err.println("读取失败: " + e.getMessage());
}
}既自动关闭资源,又在当前方法内处理了 IOException,调用方无需再 throws。
组合使用:何时用 catch,何时用 throws
| 场景 | 建议 |
|---|---|
| 当前方法能妥善处理异常(如重试、降级、记录日志后返回默认值) | 使用 try-catch 在本方法内处理 |
| 当前方法只做「透传」,由上层或调用方统一处理 | 使用 throws 声明 |
| 使用了流、连接等需关闭的资源 | 使用 try-with-resources,可再配合 catch 或 throws |
同一方法内可以:用 try-with-resources 打开资源,在 try 块内写业务逻辑,用 catch 处理受检异常,无需在签名上写 throws。
注意事项
注意
catch 顺序:若多个 catch 捕获的类型有继承关系,必须先写子类、再写父类,否则子类 catch 永远不可达,编译器报错。
// 错误示例:Exception 包含 IOException,IOException 的 catch 不可达
try {
Files.readAllLines(Path.of("x"));
} catch (Exception e) {
// ...
} catch (IOException e) { // 编译错误:不可达
// ...
}注意
finally 与 return:若 try 或 catch 中有 return,finally 仍会执行;若 finally 里也有 return,会覆盖 try/catch 的返回值,容易造成困惑,应避免在 finally 中 return。
提示
对实现了 AutoCloseable 的资源,优先使用 try-with-resources,避免在 finally 中手动 close;若 close 本身可能抛异常,需再 try-catch,代码会变得冗长且易错。
注意
不要用空的 catch 块「吞掉」异常(如 catch (Exception e) {}),至少应记录日志或做有意义的处理,否则问题难以排查。