不推荐用异常控制正常流程,因性能开销大、降低可读性与可维护性、干扰错误诊断;应改用if-else判断、预检或工具类方法。

不推荐用异常控制正常流程,这是Java设计原则中的明确禁忌。异常机制专为处理非预期、不可控的错误场景而设,而非替代if-else或状态判断。
为什么异常不适合做流程控制
异常抛出和捕获涉及JVM栈展开、填充堆栈跟踪(StackTrace),性能开销大;频繁使用会显著拖慢程序,尤其在高频路径(如循环、getter/setter)中。更关键的是,它混淆了“错误”与“业务逻辑分支”,让代码可读性、可维护性、可测试性严重下降。
- 调用方无法从方法签名(如无throws声明)预判该“异常”是否属于正常业务流转
- IDE和静态检查工具无法识别这种“伪异常”,导致空指针、类型转换等真正危险问题反而被掩盖
- 日志系统会默认记录所有异常,造成大量无效告警,干扰真实故障定位
典型误用场景及替代方案
场景1:用NullPointerException判断对象是否为空
错误写法:try { obj.doSomething(); } catch (NullPointerException e) { /* 空处理 */ }
正确做法:直接if (obj == null),或使用Objects.requireNonNull()(仅用于参数校验,非流程分支)
场景2:用NumberFormatException解析字符串数字
错误写法:靠catch来区分“是数字”还是“不是数字”
正确做法:先用正则str.matches("-?\\d+")预检,或用Apache Commons的NumberUtils.isCreatable(),再parse
立即学习“Java免费学习笔记(深入)”;
场景3:集合取值时依赖IndexOutOfBoundsException
错误写法:try { list.get(i); } catch (IndexOutOfBoundsException e) { /* 越界处理 */ }
正确做法:if (i >= 0 && i ,或使用Optional封装返回值
什么情况下可以用异常驱动逻辑
极少数合规场景存在,但必须满足三个条件:发生概率极低、无法提前预判、且属于系统级契约破坏。
- IO操作中,文件突然被删除或网络连接中断(底层资源不可控)
- 反射调用时,目标类在运行时被卸载(ClassLoader异常)
- 第三方库强制要求通过特定异常传递上下文(如某些RPC框架的业务异常透传)
即便如此,也应将这类异常包装为自定义业务异常,并在顶层统一拦截、降级或重试,而非在业务层直接catch后跳转逻辑。
如何识别和重构异常滥用代码
关注以下信号:
- catch块中没有记录日志、没做资源清理,只做简单赋值或return
- 同一个异常类型在多个无关方法中被“预期性捕获”
- 单元测试里大量使用@Test(expected = XxxException.class)验证正常路径
重构策略:提取判断条件到guard clause,把“异常分支”转为显式布尔表达式,配合Optional、Either等语义化类型提升意图表达力。









