精准捕获异常需按语义分层、顺序合理:子类异常必须置于父类前;可用|合并无继承关系的异常;避免泛化捕获,兜底catch应置于末尾且仅用于记录或转异常;日志须含上下文而非仅e.getMessage()。

精准捕获不同异常类型,关键不在“多写几个catch”,而在于按异常语义分层、顺序合理、避免掩盖。Java 的多重 catch 机制本身很清晰,但用错顺序或滥用合并,反而会让错误更难定位。
子类异常必须写在父类前面
这是编译器强制要求,不是建议。比如 FileNotFoundException 是 IOException 的子类,那必须先 catch 前者,再 catch 后者:
- ✅ 正确顺序:
catch (FileNotFoundException e)→catch (IOException e) - ❌ 错误写法:
catch (IOException e)→catch (FileNotFoundException e),编译直接报错:“exception FileNotFoundException has already been caught” - 同理,
NumberFormatException要放在IllegalArgumentException前面,NullPointerException要放在RuntimeException前面
用 | 合并处理逻辑完全一致的异常
Java 7+ 支持单个 catch 捕获多个互不继承的异常类型,前提是它们业务含义相近、恢复策略相同:
- ✅ 合法且推荐:
catch (IOException | SQLException e)(二者无继承关系,都代表外部资源失败) - ❌ 编译报错:
catch (IOException | FileNotFoundException e)(后者是前者子类,违反 disjoint 规则) - ⚠️ 注意:变量
e的静态类型是它们的最近公共父类(如Exception),不能直接调用SQLException.getSQLState(),需先instanceof判断
避免泛化捕获,兜底要有边界
不要一上来就写 catch (Exception e) 或更糟的 catch (Throwable t):
立即学习“Java免费学习笔记(深入)”;
- 它会吞掉所有子类异常,导致后续 catch 块失效,也掩盖了本该区分处理的问题
- 如果真需要兜底,应放在所有具体 catch 之后,且仅用于记录、告警或转为统一业务异常,而不是静默忽略
- 像
OutOfMemoryError这类Throwable子类,捕获后强行处理可能干扰 JVM 稳定性,通常应由监控系统捕获,而非业务代码干预
日志和上下文比异常类型更重要
捕获到异常只是开始,真正帮助排查的是上下文信息:
- 别只记
e.getMessage(),要补全:当前操作(如 "pay_order")、用户 ID、traceId、关键入参(脱敏后) - 用
logger.error("msg", e),而不是e.printStackTrace()—— 前者交由日志框架格式化,后者容易打乱日志结构 - 如果多个 catch 块日志逻辑相同,把构造日志对象、填充字段、调用 logger 的动作提取成一个私有方法,传入异常和上下文参数即可


















