防御性编程重在事前预防,通过LBYL检查参数、业务规则和边界条件;受检异常则强制处理外部不可控风险,如IO、网络、数据库操作,二者分层协作保障健壮性。

防御性编程和受检异常是 Java 异常处理中两个互补但定位不同的实践手段:前者重在“防患于未然”,后者重在“兜底与契约”。它们不是替代关系,而是在不同层级协同保障程序健壮性。
防御性编程:用 LBYL 主动拦截可预见问题
防御性编程强调在错误发生前就做显式检查,核心是提前识别并拒绝非法输入或不满足前提的调用。它适用于逻辑清晰、条件可静态判断的场景,避免把本可预防的问题推给异常机制。
- 参数校验:方法入口处用 Objects.requireNonNull()、StringUtils.isBlank() 或自定义断言验证非空、范围、格式等
- 业务规则前置:如转账前检查余额是否充足、订单状态是否允许修改,直接抛出 BusinessException 而非等到数据库约束报错
- 集合与索引安全:访问 List 元素前先 size() 判断,避免 IndexOutOfBoundsException;Map 取值前 containsKey() 比 get() != null 更明确
- 避免空指针链式调用:使用 Optional 或卫语句(guard clause)快速退出,不层层嵌套 if (obj != null && obj.getA() != null && ...)
受检异常:强制处理外部不确定性
受检异常(Checked Exception)代表程序无法完全控制的外部风险,比如文件读写、网络请求、数据库连接。Java 编译器要求你必须显式声明(throws)或捕获(try-catch),本质是一种 API 层面的契约——提醒调用者:“这件事可能失败,你得负责善后。”
- IO 类操作必须处理 IOException:打开文件、序列化、Socket 通信等,失败原因多样(磁盘满、权限不足、网络中断),无法靠预检查穷尽
- 数据库操作需应对 SQLException:连接超时、锁等待、SQL 语法错误等,依赖运行时环境,只能事后捕获并重试/回滚/降级
- 不要吞掉受检异常:catch 后空实现或只 printStackTrace() 是反模式;应记录日志、转换为更上层的业务异常,或按策略恢复
- 合理包装再抛出:底层 IOException 可包装成 DataLoadException,隐藏技术细节,暴露语义清晰的业务含义
二者如何配合使用
真实业务中,防御性编程和受检异常常分层协作:上层用防御性编程过滤明显错误,下层用受检异常应对不可控外部依赖。
- 用户提交表单时,前端+后端双重校验字段长度、邮箱格式(防御性);提交成功后调用支付接口,必须捕获 PaymentNetworkException(受检异常)并提示“支付通道暂时不可用”
- 读取配置文件前,先检查路径字符串是否为空或含非法字符(防御性);真正 FileReader.open() 时仍要 catch FileNotFoundException(受检异常),因为文件可能被外部进程删除
- 转账服务中,先校验金额 > 0、账户状态有效(防御性);再执行 JDBC update,捕获 SQLException 并启动事务回滚(受检异常)
关键区别与选型建议
选哪种方式,关键看错误来源是否可控、是否可预测:
- 错误由输入数据或业务逻辑缺陷导致 → 优先防御性编程,早暴露、易调试
- 错误由外部系统、资源状态或并发竞争引发 → 必须用受检异常(或 RuntimeException 封装)来捕获和响应
- 性能敏感路径(如高频循环)避免滥用 try-catch;但少量关键 IO 或 DB 操作,受检异常开销远小于崩溃重启
- 团队协作中,受检异常是接口契约的一部分,让调用方无法忽略失败可能;防御性编程则是内部质量守门员


















