Java 将异常分为两大阵营:
Exception 但非 RuntimeException,编译器强制要求 try-catch 或 throws 声明,代表“可预期、可恢复”的业务故障(如 IOException、SQLException)。Error(JVM 级灾难性错误)与 RuntimeException(编程逻辑缺陷,如空指针、越界),编译器不强制捕获,属于“应当预防而非捕获”的范畴。FileInputStream(String) 抛出 FileNotFoundException 即是典范:方法签名明确告知调用者“可能找不到文件”,调用者据此决定捕获处理或向上传播。这种“失败快速、契约清晰”的设计,显著提升代码健壮性。
继承 Exception 创建 InsufficientBalanceException、OrderNotFoundException 等领域异常,而非滥用通用 Exception。配合 JDK 1.4+ java.util.logging 或 SLF4J/Logback 实现集中日志记录,便于排查与审计。
try {
Data data = parser.parse(input);
repository.save(data);
} catch (ParseException e) {
log.warn("数据格式错误: {}", input, e);
throw new InvalidDataException("解析失败", e); // 保留原始堆栈
} catch (PersistenceException e) {
log.error("存储层故障", e);
throw new ServiceUnavailableException("暂时不可用", e);
}
将业务异常包装为 RuntimeException 子类(如 BizRuntimeException)以省去 throws 声明,实则掩盖了方法契约,导致调用者无感知、无处理,最终演变为难以追踪的生产事故。仅在框架层、不可恢复的系统级错误中谨慎使用。
// 反面教材
try {
// 核心业务逻辑
} catch (Throwable t) {
t.printStackTrace(); // 仅打印,不处理、不传播、不恢复
}
此模式吞噬 Error(如 OutOfMemoryError)、线程中断信号,掩盖根因,破坏故障隔离。仅允许在顶层框架(如 Servlet 容器、线程池)统一兜底记录并优雅退出。
遇到 OutOfMemoryError、磁盘写满、死锁等资源耗尽型异常,简单 catch 无法自愈。需结合:
finally 关闭流、锁、连接)为抛出异常而显式 new Exception() 分配对象、甚至在 finally 中强行 throw 覆盖原始异常,属于“为了用语言特性而用特性”的本末倒置。更严重的是:OutOfMemoryError 属于 Error 非受检异常,若代码显式 catch (OutOfMemoryError) 试图恢复,极易因元空间/堆耗尽导致 finally 块无法执行、锁无法释放,引发级联崩溃。
try 块封装一个原子业务操作,失败即整体回滚。catch 后必须 throw、rethrow 或记录日志并转为业务异常,严禁 catch { } 空块。SQLException),应包装为领域异常抛给 Service 层决策。try-with-resources (JDK 7+) 自动关闭 AutoCloseable 资源,消除 finally 样板代码。