吞食异常是指catch后不处理或仅简单打印,导致错误线索丢失、排查困难;应捕获具体异常类型并记录完整堆栈,避免空catch、宽泛捕获exception/throwable,finally中关闭资源需防止掩盖主异常,包装异常必须传入cause。

吞食异常,就是 catch 住异常后什么也不做——它不会报错,但会悄悄毁掉你的排查能力。
什么是空 catch 块?
就是写了个 catch,里面只有花括号、分号,或者只有一行 System.out.println("error") 这种不带堆栈的“假处理”:
try {
riskyOperation();
} catch (IOException e) {
// 啥都没写 ? 这就是吞食
}
这种代码能编译通过,运行也不崩溃,但一旦出问题,日志里查不到任何线索,监控看不到错误指标,用户反馈“功能没反应”,而你连哪一行崩了都不知道。
- 常见于“先占个位置,回头再加日志”的临时写法,结果上线就忘了
- 也出现在对
InterruptedException的误处理中:只调用e.printStackTrace()却没恢复中断状态 - 更隐蔽的是返回默认值(比如
catch (Exception e) { return null; }),让上层误以为操作成功
为什么不能捕获 Exception 或 Throwable?
因为它们太宽泛了:
-
catch (Exception e)会抓到NullPointerException、IllegalArgumentException这类明显是代码 bug 的异常,掩盖了本该在开发阶段暴露的问题 -
catch (Throwable t)连OutOfMemoryError、StackOverflowError都一并吞下——这些错误根本不该被业务代码捕获或尝试恢复 - 真正该捕获的,是你明确知道如何应对的异常类型,比如
FileNotFoundException、SQLException、JsonParseException
兜底可以有,但必须放在具体类型之后,且要记录完整堆栈:logger.error("xxx failed", e),而不是 logger.error("xxx failed")。
finally 里关资源,怎么避免掩盖原始异常?
这是 JVM 规范行为:如果 try 已抛异常,而 finally 又抛新异常,前者会被完全丢弃。例如:
try {
throw new IOException("read failed");
} finally {
resource.close(); // 抛出另一个 IOException
}
最终你只能看到 close() 的异常,原始读取失败原因彻底消失。
- JDK 7+ 优先用
try-with-resources,它自动抑制(addSuppressed)close异常,保留主异常 - 手动关闭时,
finally中所有可能失败的操作都得自己try-catch并仅记录,绝不抛出:try { resource.close(); } catch (IOException e) { logger.warn("close failed", e); } - 若真需保留多个异常,可用
originalException.addSuppressed(suppressedException)显式附加(JDK 7+)
包装异常时漏传 cause 是多大隐患?
写成 throw new RuntimeException("timeout") 而不是 throw new RuntimeException("timeout", e),会导致调用链断裂:
- 堆栈里只剩这一层,看不到最开始是哪个网络请求、哪次数据库查询出的问题
- 日志聚合系统无法按根因聚类,同一个底层错误反复出现却识别为不同问题
- 调试时得靠猜和试,而不是直接跳转到源头
只要不是全新定义的业务异常(如 InsufficientBalanceException),凡是基于已有异常重构的,都必须把原异常作为 cause 传入构造函数。
最容易被忽略的,不是“要不要记日志”,而是“记不记完整堆栈”;不是“该不该捕获”,而是“捕获之后有没有把上下文和因果链一起留下”。一次吞食,可能让一个 bug 在生产环境潜伏数月。










