Java 完全指南 / 12 - 异常处理:try/catch/finally、自定义异常、checked/unchecked
12 - 异常处理:try/catch/finally、自定义异常、checked/unchecked
异常体系结构
Throwable
├── Error(严重错误,不捕获)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception(可处理的异常)
├── 受检异常(Checked)—— 编译器强制处理
│ ├── IOException
│ ├── SQLException
│ └── ...
└── RuntimeException(Unchecked)—— 运行时异常
├── NullPointerException
├── ArrayIndexOutOfBoundsException
├── IllegalArgumentException
├── ClassCastException
└── ...
try-catch-finally
import java.io.*;
public class TryCatchDemo {
public static void main(String[] args) {
// 基本 try-catch
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("捕获异常: " + e.getMessage());
}
// 多重 catch
try {
String s = null;
s.length(); // NullPointerException
} catch (NullPointerException e) {
System.out.println("空指针: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他异常: " + e.getMessage());
} finally {
System.out.println("finally 总是执行");
}
// 多异常合并(JDK 7+)
try {
int[] arr = new int[5];
arr[10] = 1;
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("运行时异常: " + e.getClass().getSimpleName());
}
}
}
try-with-resources(JDK 7+)
import java.io.*;
public class TryWithResourcesDemo {
public static void main(String[] args) {
// 自动关闭实现了 AutoCloseable 的资源
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
System.err.println("IO 错误: " + e.getMessage());
}
// reader 和 writer 在 try 块结束时自动关闭
// JDK 9+ 之前声明的资源也可以
BufferedReader br = new BufferedReader(new StringReader("hello"));
try (br) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
异常用法
public class ExceptionUsageDemo {
// throws 声明抛出受检异常
public static String readFile(String path) throws IOException {
// throw 主动抛出异常
if (path == null || path.isBlank()) {
throw new IllegalArgumentException("文件路径不能为空");
}
java.nio.file.Files.readString(java.nio.file.Path.of(path));
return "ok";
}
// 异常链 —— 包装原始异常
public static void processOrder(String orderId) {
try {
if (orderId == null) throw new RuntimeException("订单ID为空");
} catch (RuntimeException e) {
throw new OrderProcessingException("处理订单失败: " + orderId, e);
}
}
// 异常信息增强
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException(
String.format("除数不能为零: %d / %d", a, b));
}
return a / b;
}
public static void main(String[] args) {
// 异常信息
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
for (StackTraceElement el : e.getStackTrace()) {
System.out.println(" at " + el);
}
}
}
}
// 自定义受检异常
class OrderProcessingException extends Exception {
public OrderProcessingException(String message) {
super(message);
}
public OrderProcessingException(String message, Throwable cause) {
super(message, cause);
}
}
// 自定义非受检异常
class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() { return code; }
}
Checked vs Unchecked
| 维度 | Checked 异常 | Unchecked 异常 |
|---|---|---|
| 基类 | Exception(非 RuntimeException) | RuntimeException |
| 编译检查 | ✅ 必须处理 | ❌ 不强制 |
| 处理方式 | try-catch 或 throws | 可以不处理 |
| 典型 | IOException, SQLException | NPE, IAE |
| 设计意图 | 可恢复的错误 | 编程错误 |
何时使用哪种异常
// ✅ Checked —— 调用者可以且应该处理
public class FileService {
public String readConfig(String path) throws IOException {
// 文件可能不存在,这是正常的可恢复情况
return Files.readString(Path.of(path));
}
}
// ✅ Unchecked —— 编程错误,调用者不应捕获
public class UserService {
public void createUser(String name) {
if (name == null) {
throw new NullPointerException("name"); // 编程错误
}
if (name.isBlank()) {
throw new IllegalArgumentException("name"); // 参数校验失败
}
}
}
异常处理最佳实践
public class ExceptionBestPractices {
// ❌ 不要捕获 Exception 或 Throwable
// void bad() { try { ... } catch (Exception e) { } }
// ❌ 不要吞掉异常
// void bad2() { try { ... } catch (Exception e) { /* 什么都不做 */ } }
// ✅ 精确捕获
void good() {
try {
riskyOperation();
} catch (FileNotFoundException e) {
log.warn("配置文件不存在,使用默认配置");
} catch (IOException e) {
log.error("读取文件失败", e);
throw new ServiceException("服务不可用", e);
}
}
// ✅ try-with-resources 确保资源关闭
void copyFile(String src, String dst) throws IOException {
try (var in = new FileInputStream(src);
var out = new FileOutputStream(dst)) {
in.transferTo(out);
}
}
// ✅ 使用 Optional 代替返回 null
java.util.Optional<String> findUser(String id) {
return java.util.Optional.ofNullable(getFromDB(id));
}
}
⚠️ 注意事项
- 不要用异常控制流程 — 异常开销大,应用于异常情况。
- catch 块要有意义 — 至少记录日志,不要吞掉异常。
- finally 不要使用 return — 会覆盖 try/catch 中的 return 值。
- 不要在 finally 中抛异常 — 会覆盖 try 中的异常。
💡 技巧
全局异常处理器(Spring Boot):
@RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<?> handleBiz(BusinessException e) { return ResponseEntity.badRequest().body(Map.of("code", e.getCode(), "msg", e.getMessage())); } }Supplier 延迟构造异常(避免不必要的堆栈计算):
static <T> T requireNonNull(T obj, Supplier<String> msg) { if (obj == null) throw new NullPointerException(msg.get()); return obj; }
🏢 业务场景
- Web 服务: 统一异常处理返回标准错误 JSON。
- 数据库操作:
SQLException是 checked,需要 try-with-resources 关闭连接。 - 文件处理:
IOException处理文件不存在、权限不足等情况。 - 微服务: 熔断降级时捕获远程调用异常,返回兜底数据。