一句话总结
Throwable 分两支:Error(JVM 级错误,不应捕获)和 Exception。Exception 又分受检异常(编译期强制处理,如 IOException)和非受检异常(RuntimeException,如 NPE)。最佳实践:try-with-resources 自动关资源、全局异常处理器统一处理、自定义业务异常继承 RuntimeException。
初级理解
Java 异常的顶层类是 Throwable,分为两大分支:
Error:程序无法处理的严重错误,如 OutOfMemoryError、StackOverflowError、NoClassDefFoundError。这些错误通常由 JVM 产生,程序不应该也不建议去捕获处理。
Exception:程序可以处理的异常,又分为两类:
受检异常(Checked Exception):编译时必须处理的异常(try-catch 或 throws),如 IOException、SQLException、ClassNotFoundException。如果不处理,代码无法编译通过。
非受检异常(Unchecked Exception):RuntimeException 及其子类,编译时不强制处理,如 NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException。通常是程序逻辑错误导致。
// 受检异常:必须处理
public void readFile() throws IOException {
FileReader reader = new FileReader("test.txt");
}
// 非受检异常:可以不处理
public int divide(int a, int b) {
return a / b; // 可能抛出 ArithmeticException
}
中级深入
try-with-resources(JDK 7):实现了 AutoCloseable 接口的资源可以自动关闭,无需在 finally 中手动关闭。
// 传统写法
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("test.txt"));
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try { br.close(); } catch (IOException e) { }
}
}
// try-with-resources
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
} // br 自动关闭
异常链(Exception Chaining):在捕获一个异常后抛出另一个异常时,应该把原始异常作为 cause 传入,保留完整的异常链路。
try {
// 数据库操作
} catch (SQLException e) {
throw new BusinessException("业务处理失败", e); // 保留原始异常
}
finally 中的 return 陷阱:finally 中的 return 会覆盖 try/catch 中的 return 值和抛出的异常。
高级拓展
全局异常处理设计(Spring Boot 示例):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors()
.stream().map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, msg);
}
@ExceptionHandler(Exception.class)
public Result handleUnknown(Exception e) {
log.error("未知异常", e);
return Result.error(500, "服务器内部错误");
}
}
异常处理最佳实践:
1. 不要吞掉异常(空 catch 块),至少记录日志
2. 尽量使用具体异常类而非泛化的 Exception
3. 在合适的层级处理异常(底层抛出,上层处理)
4. 自定义业务异常继承 RuntimeException,避免受检异常的传播污染
5. 异常信息要包含足够的上下文(用户ID、订单号等),方便排查
面试加分项:能说出全局异常处理的设计方案和异常处理最佳实践,说明你有实际项目经验。
实战场景
场景:自定义业务异常体系
// 基础业务异常
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() { return code; }
}
// 具体业务异常
public class OrderNotFoundException extends BusinessException {
public OrderNotFoundException(Long orderId) {
super(404, "订单不存在: " + orderId);
}
}
// 使用
if (order == null) {
throw new OrderNotFoundException(orderId); // 全局异常处理器统一捕获
}
场景:异常链保留原始信息
// 反例:丢失原始异常
try {
userService.create(user);
} catch (SQLException e) {
throw new BusinessException("创建用户失败"); // 原始异常丢失!
}
// 正例:保留异常链
try {
userService.create(user);
} catch (SQLException e) {
throw new BusinessException("创建用户失败", e); // 原始异常作为 cause
}
面试模拟
Q:受检异常和非受检异常的区别?什么时候用哪个?
A:受检异常编译期强制处理(try-catch 或 throws),非受检异常不强制。受检异常用于可恢复的情况(如文件不存在可重试),非受检异常用于编程错误(如 NPE、参数校验失败)。现代框架(Spring)倾向于使用非受检异常,避免异常传播污染。
Q:try-with-resources 的原理是什么?
A:实现了 AutoCloseable 接口的资源可以在 try() 中声明,编译后会自动生成 finally 块调用 close()。多个资源用分号分隔,关闭顺序与声明顺序相反。JDK 9 支持在 try 中使用已声明的 effectively final 变量。