在Java中,应将受检异常包装为继承RuntimeException、字段final且构造时传入cause的不可变业务异常;catch中需用new BusinessException(msg, e)传递原始异常,确保栈追踪完整、信息可追溯,避免继承Exception或忽略cause。

在 Java 中,将受检异常(checked exception)在 catch 块中包装为自定义的不可变业务异常,核心是:捕获原始异常、构造新异常时传入 cause、确保业务异常类本身不可变(即字段 final + 无 setter)、并继承 RuntimeException 使其成为非受检异常。
定义不可变的业务异常类
业务异常应继承 RuntimeException,所有字段声明为 final,仅通过构造函数初始化,不提供修改方法。推荐至少保留原始异常作为 cause,并可选携带业务错误码、提示信息等:
- 构造函数必须调用
super(throwable)或super(message, throwable),以正确链式传递 cause - 避免在构造函数中做复杂逻辑或外部调用,保证对象创建过程安全、轻量
- 若需错误码,建议用枚举或不可变字符串,而非可变集合或对象
在 catch 块中完成包装
捕获受检异常后,直接 new 一个你的业务异常实例,把原异常作为 cause 传入。JVM 会自动维护异常栈追踪链:
- 不要忽略原始异常(如只打印不 re-throw),否则丢失关键诊断信息
- 不要用
new BusinessException("出错了")这种无 cause 的方式,会切断异常根源 - 可补充上下文信息,例如:
new OrderValidationException("订单 " + orderId + " 校验失败", e)
确保异常信息可读且可追溯
不可变不等于信息贫乏。合理利用 getMessage() 和 getCause() 提供分层信息:
立即学习“Java免费学习笔记(深入)”;
- 业务异常的 message 面向开发者或日志,说明发生了什么业务问题
- 原始异常的 message 和 stack trace 保留在 cause 中,便于排查底层原因(如数据库连接超时、文件不存在)
- 日志框架(如 SLF4J)打印异常时,默认会递归输出 cause,无需额外处理
避免常见陷阱
几个容易出错的点:
- 业务异常类误继承
Exception:会导致调用方仍需显式 try-catch,违背“包装为运行时异常”的初衷 - 字段未声明 final 或提供了 public setter:破坏不可变性,可能被恶意修改或并发问题
- 在 catch 中吞掉异常(空 catch 或只 log 不 throw):导致上游无法感知失败,流程静默中断
- 包装多层后 cause 链过长:只要每层都正确传入 cause,调试工具(如 IDE、jstack)仍能逐层展开,无需刻意扁平化


















