异常处理

异常体系

Throwable
├── Error(不应捕获,JVM 级别严重问题)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── VirtualMachineError
└── Exception
    ├── RuntimeException(非受检异常,编译器不强制处理)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ClassCastException
    │   ├── ArithmeticException
    │   ├── IllegalArgumentException
    │   ├── IllegalStateException
    │   └── UnsupportedOperationException
    └── 受检异常(Checked Exception,必须声明或处理)
        ├── IOException
        ├── SQLException
        └── ClassNotFoundException

受检 vs 非受检

  • 受检异常:编译器强制要求 try-catchthrows 声明,代表可预期的外部故障(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("");

相关链接