异常处理
异常体系
Throwable
├── Error(不应捕获,JVM 级别严重问题)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception
├── RuntimeException(非受检异常,编译器不强制处理)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── ArithmeticException
│ ├── IllegalArgumentException
│ ├── IllegalStateException
│ └── UnsupportedOperationException
└── 受检异常(Checked Exception,必须声明或处理)
├── IOException
├── SQLException
└── ClassNotFoundException
受检 vs 非受检:
- 受检异常:编译器强制要求
try-catch或throws声明,代表可预期的外部故障(IO、网络) - 非受检异常(RuntimeException):通常代表编程错误,不强制处理
基本语法
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("除零错误: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他异常: " + e.getMessage());
} finally {
// 无论是否发生异常都执行,常用于释放资源
System.out.println("finally 块");
}执行顺序规则:
finally一定执行(即使有return)catch从上到下匹配,子类异常必须放在父类之前- 若
finally中有return,会覆盖try/catch中的return
多异常捕获(Java 7+)
try {
// ...
} catch (IOException | SQLException e) {
// 合并处理,e 隐式为 final
log.error("数据操作失败", e);
}try-with-resources(Java 7+)
实现了 AutoCloseable 的资源自动关闭,无需手动 finally:
try (
InputStream in = new FileInputStream("a.txt");
OutputStream out = new FileOutputStream("b.txt")
) {
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
} catch (IOException e) {
log.error("文件操作失败", e);
}
// 离开 try 块后自动调用 close(),顺序与声明相反若 close() 也抛出异常,会被压制(suppressed),可通过 e.getSuppressed() 获取。
抛出异常
public void transfer(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("转账金额必须大于 0,实际: " + amount);
}
if (balance < amount) {
throw new InsufficientFundsException("余额不足");
}
}
// 受检异常必须在方法签名中声明
public void readFile(String path) throws IOException {
Files.readAllBytes(Path.of(path));
}自定义异常
// 非受检异常(继承 RuntimeException,推荐)
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getCode() { return code; }
}
// 使用
throw new BusinessException("USER_NOT_FOUND", "用户不存在: " + userId);异常链
保留原始异常信息,避免信息丢失:
try {
// 底层操作
conn.prepareStatement(sql);
} catch (SQLException e) {
// 包装为业务异常,保留原因
throw new DataAccessException("查询用户失败", e);
}
// 获取原始异常
Throwable cause = e.getCause();异常信息
e.getMessage() // 异常消息
e.getLocalizedMessage() // 本地化消息
e.getCause() // 原因异常
e.getClass().getName() // 异常类名
e.printStackTrace() // 打印堆栈(生产环境用日志框架代替)
e.getStackTrace() // StackTraceElement 数组最佳实践
捕获具体异常,不要滥用 Exception:
// 不推荐
catch (Exception e) { log.error(e); }
// 推荐
catch (FileNotFoundException e) { /* 文件不存在处理 */ }
catch (IOException e) { /* IO 错误处理 */ }不要吞掉异常:
// 危险!调试困难
catch (Exception e) { }
// 至少记录日志
catch (Exception e) { log.error("操作失败", e); }异常中携带上下文信息:
throw new IllegalArgumentException(
"Invalid userId: " + userId + ", must be positive");统一异常处理(Spring Boot):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleAll(Exception e) {
log.error("系统异常", e);
return Result.fail("SYSTEM_ERROR", "系统繁忙");
}
}能用 Optional 处理 null 时,不要依赖 NullPointerException:
// 不推荐
try {
return user.getAddress().getCity();
} catch (NullPointerException e) { return ""; }
// 推荐
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("");