非受检异常本质是本该预防的逻辑缺陷,应优先通过主动校验从源头拦截;业务约束需自定义非受检异常并全局统一处理;仅在调用第三方SDK、解析外部数据等特定场景才合理捕获。

非受检异常(如 NullPointerException、ArrayIndexOutOfBoundsException)本质不是“不可预期的错误”,而是**本该被预防的逻辑缺陷**。它们不需要强制捕获,但绝不能放任不管——关键在于从源头拦截,而非事后兜底。
优先做主动校验,而不是等异常发生
把防御性检查写在业务逻辑入口,比 catch 更高效、更易定位问题:
- 用
Objects.requireNonNull(obj, "user must not be null")替代等到调用时才空指针 - 参数非法时立刻抛
IllegalArgumentException,附带明确上下文(如 “orderAmount 必须大于 0”) - 集合操作前先判空或检查索引范围:
if (list != null && !list.isEmpty()) { ... } - 数字转换前预检:
StringUtils.isNumeric(str)或正则匹配,避免NumberFormatException
区分系统 bug 和业务拒绝,用自定义非受检异常
用户输入违规、余额不足、重复提交等,属于可预期的业务约束,不该用 RuntimeException 原生类糊弄:
- 定义清晰命名的子类,如
InsufficientBalanceException、InvalidOrderStatusException - 继承
RuntimeException,不强迫调用方声明,保持 API 轻量 - 构造时传入错误码、语义化消息、可选的原始异常(
cause),方便日志追踪和前端识别 - 配合全局处理器(如 Spring 的
@RestControllerAdvice)统一返回结构化错误响应
顶层兜底 + 日志监控,确保问题不沉默
即便做了充分预防,线上仍可能因环境差异或未知路径触发非受检异常。这时需要的是可观测性,不是掩盖:
- 在应用最外层(如 Web 入口、任务调度器)设置统一异常处理器,记录
ERROR级日志 - 日志中必须包含:异常类型、消息、完整堆栈、关键业务 ID(如订单号)、脱敏后的参数快照
- 对高频出现的非受检异常(例如某接口每分钟 NPE 超 5 次)配置实时告警,视为代码稳定性风险
- 避免在业务方法里泛捕
catch (RuntimeException e),尤其不要只打印日志后吞掉异常
明确哪些场景真需要 try-catch
绝大多数业务方法不该自己捕获非受检异常。真正合理的捕获场景极少且具体:
- 调用第三方 SDK,它内部明确文档说明会抛
IllegalArgumentException表示重试可行,你已约定处理策略 - 解析外部不可控数据(如用户上传的 CSV),需容错跳过单条脏数据,不影响整体流程
- UI 层为防止界面崩溃,做最外层兜底并友好提示(但日志仍要上报)
- 资源清理逻辑中,用
try-finally保证关闭,而非靠 catch 异常来驱动

















