一、异常基础
-
Java异常处理机制是怎样的?try、catch、finally、throw、throws的区别是什么?
- 机制:通过
try定义可能抛出异常的代码块,catch捕获并处理特定异常,finally确保资源释放(无论是否发生异常),throw主动抛出异常对象,throws在方法签名中声明可能抛出的异常。 - 区别:
try:包裹风险代码。catch:捕获并处理指定异常类型。finally:必执行块,用于清理资源。throw:在方法内部抛出异常实例(e.g.,throw new IOException())。throws:声明方法可能抛出的异常类型(e.g.,public void read() throws IOException)。
- 机制:通过
-
自定义异常如何创建和使用?
- 创建:继承
Exception(检查型异常)或RuntimeException(非检查型异常)。public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } - 使用:通过
throw抛出,在方法中处理或声明。
- 创建:继承
-
Error和Exception的区别是什么?如何处理?
- 区别:
Error:JVM严重问题(如OutOfMemoryError),程序无法恢复。Exception:程序可处理的异常(如IOException)。
- 处理:
Error:通常不捕获,需修复代码或调整环境。Exception:通过try-catch处理或向上传播。
- 区别:
-
检查型异常(Checked)和非检查型异常(Unchecked)的区别?
- 检查型异常:
- 继承自
Exception(非RuntimeException)。 - 必须被捕获或在方法签名中声明(e.g.,
IOException)。
- 继承自
- 非检查型异常:
- 继承自
RuntimeException或Error。 - 无需强制处理(e.g.,
NullPointerException,ArrayIndexOutOfBoundsException)。
- 继承自
- 检查型异常:
-
Java异常处理的优缺点?
- 优点:分离正常逻辑与错误处理;类型安全;集中化处理。
- 缺点:代码冗余;性能开销(构造栈跟踪);过度使用破坏代码可读性。
-
注意事项
- 避免空
catch块(至少记录日志)。 - 优先捕获具体异常,再捕获通用异常。
finally中避免return或抛出异常,会覆盖try/catch的行为。- 使用
try-with-resources自动管理资源(实现AutoCloseable接口)。
- 避免空
-
常见陷阱
- 捕获
Throwable/Exception后忽略(吞没异常)。 - 在循环内部捕获异常导致性能问题。
- 异常类型不匹配导致未被捕获。
- 捕获
-
性能影响
- 创建成本:构造异常对象(尤其是填充栈跟踪)较慢。
- 最佳实践:异常仅用于错误场景,避免用异常控制流程(如替代
if判断)。
-
调试方法
- 打印完整堆栈:
e.printStackTrace()(仅限调试)。 - 使用IDE的调试器捕获异常断点。
- 日志记录异常链(
logger.error("Error", e))。
- 打印完整堆栈:
-
日志记录方法
- 使用日志框架(如SLF4J+Logback)记录异常:
try { // ... } catch (Exception e) { logger.error("Operation failed", e); // 记录完整堆栈 }
- 使用日志框架(如SLF4J+Logback)记录异常:
二、异常处理
-
捕获所有类型的异常
try { // ... } catch (Throwable t) { // 捕获Error和Exception logger.error("Caught throwable", t); }注意:谨慎使用,通常仅在最外层捕获
Throwable(如线程入口)。 -
抛出异常
if (file == null) { throw new IllegalArgumentException("File cannot be null"); } -
声明异常
public void readFile() throws IOException { // ... } -
处理多个异常
try { // ... } catch (IOException e) { // 处理IO异常 } catch (SQLException e) { // 处理SQL异常 } catch (Exception e) { // 兜底处理 }Java 7+:多重捕获(Multi-catch)
catch (IOException | SQLException e) { // 合并处理 } -
嵌套异常处理
try { try { // 内层代码 } catch (SpecificException e) { // 内层处理 throw new WrapperException("Context", e); // 包装异常 } } catch (WrapperException e) { // 外层处理 } -
异常链(Chained Exceptions)
- 通过构造器传递原始异常:
throw new ServiceException("Failed to call API", originalException); - 获取原始异常:
e.getCause()。
- 通过构造器传递原始异常:
-
资源释放
- 传统方式:在
finally中关闭资源。 - 现代方式:使用
try-with-resources(Java 7+):try (FileInputStream fis = new FileInputStream("file.txt")) { // 使用资源 } // 自动调用fis.close()
- 传统方式:在
-
线程中的异常
- 实现
UncaughtExceptionHandler:Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { logger.error("Uncaught exception in thread: " + thread.getName(), throwable); });
- 实现
-
异步操作中的异常
- Future:通过
Future.get()抛出ExecutionException(用e.getCause()获取原始异常)。 - CompletableFuture:用
exceptionally()或handle()处理异常。 - 线程池:重写
afterExecute()方法(ThreadPoolExecutor)。
- Future:通过
-
远程调用中的异常
- 序列化异常(实现
Serializable)。 - 定义通用错误码和异常包装类(如
RpcException)。 - 服务端记录详细日志,客户端接收简化的错误信息。
- 序列化异常(实现
三、异常设计
-
设计自定义异常
- 继承合适的父类(业务异常选
RuntimeException)。 - 添加构造器(支持异常链)。
- 包含业务相关字段(如错误码):
public class PaymentFailedException extends RuntimeException { private String errorCode; public PaymentFailedException(String code, String message) { super(message); this.errorCode = code; } }
- 继承合适的父类(业务异常选
-
设计异常层次结构
graph TD BaseBizException -->|extends| InvalidInputException BaseBizException -->|extends| PaymentException PaymentException -->|extends| InsufficientFundsException PaymentException -->|extends| CardDeclinedException- 原则:按业务领域分层,避免过度细化。
-
异常处理策略
- 分层处理:
- Controller层:捕获所有异常,返回用户友好错误。
- Service层:抛出业务异常,避免处理无关逻辑。
- DAO层:抛出数据访问异常(如
DataAccessException)。
- 统一异常处理器(Spring的
@ControllerAdvice)。
- 分层处理:
-
异常日志记录
- 记录关键信息:时间、线程、异常类型、堆栈、业务参数。
- 不同级别:
ERROR:系统错误(如数据库连接失败)。WARN:可恢复问题(如重试成功)。
- 避免重复记录(如底层已记录,上层不再记录)。
-
异常监控系统
- 集成APM工具(如Prometheus+Grafana, ELK, Sentry)。
- 监控指标:异常频率、类型分布、影响范围。
- 设置报警阈值(如1小时内同异常出现50次)。
-
异常恢复机制
- 重试:对瞬态错误(网络超时)有效。
- 事务回滚:数据库操作失败时回滚。
- 状态检查:恢复前验证系统状态(如检查文件是否存在)。
-
异常重试机制
int maxRetries = 3; for (int i = 0; i <= maxRetries; i++) { try { callExternalService(); break; } catch (RetryableException e) { if (i == maxRetries) throw e; Thread.sleep(1000 * i); // 指数退避 } }工具:Spring Retry, Resilience4j.
-
异常降级机制
- 主逻辑失败时返回备用结果:
public Data getData() { try { return fetchFromRemote(); } catch (Exception e) { return loadFromCache(); // 降级到缓存 } }
- 主逻辑失败时返回备用结果:
-
熔断机制
- 原理:当错误率超过阈值,熔断器打开,后续请求快速失败。
- 工具:Hystrix, Resilience4j, Sentinel。
- 状态机:Closed → Open → Half-Open → Closed。
-
异常隔离机制
- 线程池隔离:不同服务使用独立线程池,避免雪崩。
- 信号量隔离:限制并发调用数(如Semaphore)。
- 容器隔离:微服务部署在不同容器/节点。
总结关键点:
- 基础:理解异常分类(Error/Exception, Checked/Unchecked)和关键字用法。
- 实践:优先用
try-with-resources管理资源,避免吞没异常,合理使用日志。 - 设计:自定义异常要有业务意义,结合重试/降级/熔断构建鲁棒系统。
- 性能:异常不应频繁用于控制流,栈跟踪生成有开销。