直接 re-throw(throw e;)最安全,保留原始堆栈;转业务异常须用双参构造传 cause;日志需传异常对象并配置全堆栈输出。

直接 re-throw 是最安全的堆栈保留方式
如果只是想捕获后继续向上抛,不改异常类型,就别新建对象。用 throw e; 即可,原始堆栈从最初抛出处开始,中间 catch 行不会插入新帧。
注意两点:
- 方法签名里得声明 throws 该异常类型,否则编译不过
- 日志要配合写对:用 logger.error("读取失败", e),不是 "读取失败: " + e——后者只输出 toString(),堆栈全丢
包装成业务异常时必须显式传 cause
当需要把底层异常(比如 SQLException)转为语义清晰的业务异常(比如 UserRegisterException),关键动作是:把原始异常作为 cause 传进新异常构造器。
正确写法:
立即学习“Java免费学习笔记(深入)”;
- throw new UserRegisterException("用户注册失败", e); —— 双参构造,message + cause
- e 必须是捕获到的那个异常变量,不能是 null
- 自定义异常类里,构造函数中必须调用 super(message, cause)
错误写法:
- throw new UserRegisterException("DB error: " + e.getMessage()); —— 拼字符串,cause 为空,堆栈断链
- throw new RuntimeException(e); —— RuntimeException 单参构造把 e 当 message 处理,不是 cause
避免 initCause 的误用和陷阱
initCause 不是通用补救手段,它只能调一次,且仅在 cause 还没被设过时才生效。现代代码基本不需要它。
适用场景极窄:
- 老异常类没提供带 cause 的构造器,又不能改源码
- 创建异常对象后,动态判断是否要设 cause
更稳妥的做法是:重构异常类,加一个 public XxxException(String msg, Throwable cause) 构造器,并在内部调用 super(msg, cause)。
日志和调试时要看到完整链路
即使代码里链建对了,日志打错也白搭。
- 记录日志必须传整个异常对象:log.error("业务处理异常", ex)
- 确认日志框架配置支持全堆栈输出,比如 Logback 用 %ex,Log4j2 用 %throwable{full}
- 调试时别只看 ex.getCause(),有时原始异常藏在 ex.getSuppressed() 里(尤其 try-with-resources 场景)
- 最可靠的方式是直接调 ex.printStackTrace() 看完整输出,里面有 “Caused by” 和 “Suppressed” 分段


















