业务代码中不该用自定义异常替代if-else,因其本质是表达意外而非控制流程;将可预期的业务状态(如参数错误、权限不足)抛异常会混淆语义、拖慢性能(异常开销是if判断的10倍以上),且阻碍JIT优化;应改用Result、Optional或枚举等轻量方式表达正常分支。

业务代码里不该用自定义异常来替代 if-else,核心原因是:异常不是流程开关,而是意外信号。一旦把“用户没权限”“参数格式错”“余额不足”这类可预期、可预判的业务状态包装成异常再捕获,就混淆了错误与分支,既拖慢性能,又模糊语义。
哪些情况属于典型滥用
这些写法看着简洁,实则违背设计本意:
- 为判断手机号是否合法,直接 try-catch NumberFormatException,而不是先用正则或 NumberUtils.isDigits() 校验
- Map.get(key) 返回 null 后,不判空,反而 throw new BusinessException("用户不存在") 再 catch 处理
- 循环中反复解析 JSON 字符串,靠捕获 JsonParseException 跳出,而不是用 Jackson 的 JsonNode.has() 或预校验结构
- 权限检查写成 try { checkPermission(); doAction(); } catch (PermissionDeniedException e) { return "无权操作"; },其实 if (!user.hasRole("admin")) 更直白高效
为什么性能会明显下降
每次抛异常,JVM 都要生成完整堆栈快照、填充调用链、展开栈帧——这个过程开销远大于一次 if 判断。实测显示,高频抛 catch 异常的代码,吞吐量可能跌到原来的 1/10,尤其在 for 循环或高并发接口中尤为明显。
更隐蔽的问题是 JIT 编译器会对含大量异常路径的代码降级优化,导致长期运行后性能进一步劣化。
立即学习“Java免费学习笔记(深入)”;
什么情况下可以用自定义异常
它该用于表达“本不该发生、调用方难以预料、需要中断当前流程”的情形:
- 构造函数或 setter 中对必填字段做非空/范围校验,抛 IllegalArgumentException —— 这是防御性编程,不是流程控制
- 支付扣款时,第三方通道返回未知错误码且无法重试,此时抛 PayServiceUnavailableException 表明服务不可用
- 数据库事务提交失败且已回滚,上层需感知并终止整个业务流程,而非静默吞掉
关键判断标准:这个“异常”是不是调用方无法通过前置条件规避、也无法靠简单逻辑绕过?如果是,才值得建模为异常。
更清晰的替代方案
对绝大多数业务状态,推荐显式、轻量、可组合的表达方式:
- 返回 Result<T> 包装成功/失败,含 code、message、data 字段
- 用枚举定义业务状态,如 OrderStatus.INVALID、PaymentResult.INSUFFICIENT_BALANCE
- 工具类方法统一返回 Optional,让调用方决定 isPresent() 还是 orElseThrow()
- 校验逻辑抽成独立 service 方法,返回 boolean 或 ValidationError 列表,不抛异常
这样主流程干净,测试容易覆盖,日志也只记录真正意外,不会被“每天几千次的参数校验失败”刷屏。


















