异常捕获应按业务语义和处理动作划分粒度,而非机械堆砌类型;优先子类具体处理,父类兜底;自定义异常锚定业务场景;多异常合并仅限无继承关系且补偿动作完全一致时。

异常捕获不是越细越好,也不是越粗越省事——关键在“该分则分,该合则合”。粒度太细,满屏catch;粒度太粗,所有错误都变成“系统异常”,查不出哪笔订单库存校验失败、哪个用户上传的文件路径不存在。真正的平衡点,藏在业务语义和处理动作里。
按处理动作区分,而非按异常类型堆砌
写多个catch,不是因为抛出了5种异常,而是因为这5种异常需要5种不同响应。比如:
- 除零异常(ArithmeticException)→ 返回“金额不能为零”,前端高亮输入框
- 空指针(NullPointerException)→ 记录缺失参数名,触发告警并降级返回默认值
- 文件未找到(FileNotFoundException)→ 提示“模板文件丢失,请联系管理员”
- 其余IO问题(IOException)→ 统一重试一次,失败则标记任务待人工介入
如果后三者都只需记日志+返回通用错误码,那就没必要拆成三个catch,用catch (FileNotFoundException | SecurityException | EOFException e)合并更干净。
子类优先,但别为继承关系过度拆解
顺序必须是具体到宽泛:子类异常写在前面,父类兜底写在最后。但这不等于要把所有可能的子类全列出来。例如处理数据库操作:
-
catch (SQLTimeoutException e)→ 触发熔断,跳过当前批次 -
catch (SQLIntegrityConstraintViolationException e)→ 解析错误码,提示“手机号已注册” -
catch (SQLException e)→ 兜底记录完整SQL与参数,交由DBA分析
不需要再单独捕获SQLNonTransientException,因为它只是SQLException的父类,且本身不携带业务含义;也不必把每个MySQL特有异常都列一遍,除非它们对应明确、差异化的业务策略。
用自定义异常锚定业务边界
Java内置异常只描述“怎么错”,不说明“为什么错”。真正需要区分的,是业务场景:
-
InsufficientStockException→ 库存不足,允许前端展示“补货中”状态 -
InvalidPromoCodeException→ 优惠码无效,需返回具体失效原因(过期/已使用/不匹配) -
PaymentDeclinedException→ 支付被拒,触发风控复核流程
这些异常由业务代码主动抛出,上层用对应catch精准拦截。比起层层判断e.getMessage().contains("stock"),语义清晰、调用方可直接做决策,日志聚合也更容易统计各环节失败率。
合并处理时守住两个底线
用竖线|在一个catch里写多个异常类型,只在满足以下条件时才安全:
- 这些异常之间没有继承关系(编译器强制检查)
- 它们触发的是完全相同的补偿动作:关资源、发告警、返回同一HTTP状态码
- 日志上下文字段一致(如都带
orderId和traceId),无需差异化提取信息
一旦其中某类异常后续要加特殊逻辑(比如SocketTimeoutException需记录网络延迟,而SQLException要抽慢SQL),就得立刻拆开——合并是为了简洁,不是为了偷懒。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











