异常不该用于流程控制。应避免用NoSuchElementException、NumberFormatException等代替if判断,改用hasNext()、正则校验等;检查型异常需按业务恢复能力决定声明或包装;日志须保留异常链并脱敏敏感信息;自定义异常应具业务语义与结构化字段。

异常该不该用在流程控制里
不该。把 Exception 当作 if 用是 Java 中最典型的滥用——比如用 NoSuchElementException 捕获 Iterator.next() 来判断是否还有元素,或靠 NumberFormatException 判断字符串是否为数字。
这类写法会让调用栈膨胀、JVM 无法优化、日志被无效异常刷屏,且掩盖真实错误。
- 替代方案:用
hasNext()而非捕获NoSuchElementException - 校验字符串数字:先用正则
str.matches("\\d+")或 Apache Commons 的NumberUtils.isDigits(str),而非直接Integer.parseInt() - 集合查找:优先用
Optional.ofNullable(map.get(key)).isPresent(),而不是 try-catchNullPointerException
检查型异常(Checked Exception)何时该声明,何时该包装
Java 强制处理的 IOException、SQLException 等不是“必须层层 throws”,关键看调用方能否合理恢复。
如果上层业务根本无法重试、补偿或降级(比如 Web 层收到一个数据库连接失败的 SQLException),强行声明只会污染接口、增加无意义的 try-catch 套娃。
立即学习“Java免费学习笔记(深入)”;
- 建议:在 DAO 层捕获原始
SQLException,包装为运行时异常如DataAccessException(Spring 就这么干) - 若确实需要提示调用方处理(如文件路径可能不存在,但业务允许 fallback 到默认配置),可自定义 checked 异常,但命名要明确语义,例如
ConfigFileNotFoundException - 避免泛化:不要把所有 IO 错误都 throw
Exception,也不要让 service 方法 signature 出现throws IOException, SQLException, InterruptedException这种组合
日志 + 异常信息该怎么打才不丢上下文
只写 e.printStackTrace() 或 log.error("出错了", e) 是常见失控行为——前者输出到 stdout 不可控,后者在异步线程或 logback 异步 appender 下可能丢失 MDC 上下文。
更糟的是,很多开发者在 catch 块里只记录日志却不 re-throw,导致上游完全感知不到失败。
- 必须保留原始异常链:用
log.error("查询用户 {} 失败", userId, e),而不是log.error("查询用户失败: " + e.getMessage()) - 敏感字段脱敏:打印前过滤
password、token等字段,避免日志泄露 - 异步场景:手动将 MDC 内容传入新线程,或改用
StructuredArgument(如 Logback 的Markers)显式携带上下文
自定义异常别只继承 Exception 就完事
一个空的 class ValidationException extends Exception 没有价值。真正影响健壮性的,是异常能否驱动下游决策。
比如支付失败,前端需要区分“余额不足”(引导充值)、“风控拒绝”(提示联系客服)、“参数错误”(前端校验修复)——这三者必须是不同异常类型,且携带结构化错误码和可展示消息。
- 推荐模式:所有业务异常继承统一基类
BizException,含errorCode(String)、httpStatus(int)、userMessage(i18n key)字段 - 避免在异常构造器里拼接动态字符串,防止
toString()触发 NPE 或慢日志;消息模板用 SLF4J 风格占位符,在 log 时再填充 - 不要让异常实现
Serializable除非明确需跨 JVM 传输(如 Dubbo 远程调用);序列化字段变更极易引发兼容问题
public class PaymentRejectedException extends BizException {
public PaymentRejectedException(String orderId) {
super("PAYMENT_REJECT", HttpStatus.PAYMENT_REQUIRED,
"payment.reject.user_message", orderId);
}
}
异常不是错误的终点,而是系统间协商失败语义的契约。写错一行 throw,可能让整个熔断策略失效,或让前端弹出“null”这种字面量错误提示。










