Java中应直接用throw e重抛异常以保留堆栈轨迹,避免新建异常丢失信息;throws仅声明不传播异常;包装异常优先用带cause构造函数;禁用空catch和printStackTrace()。

Java中用throw重新抛出捕获的异常
直接用throw语句把当前catch块中捕获的异常对象再抛出去,是最常见也最安全的重新抛出方式。它保留原始异常的堆栈轨迹,对调试至关重要。
常见错误是新建一个同类型异常再抛,比如throw new IOException(e.getMessage())——这会丢失原始StackTraceElement,让问题定位变困难。
- 正确写法:
try { // 可能抛出IOException } catch (IOException e) { // 做些日志或清理 throw e; // 直接重抛,不改写法 } - 如果必须包装,用构造函数传入
cause:throw new RuntimeException("处理失败", e); - 不要在
finally里无条件throw,否则会覆盖try或catch中已有的异常
throws声明与异常传播链的关系
throws只是告诉编译器“这个方法可能向上抛出这些异常”,它本身不触发传播,也不影响运行时行为。真正决定异常是否继续向上传的是throw语句的位置和是否有匹配的catch。
容易混淆的点:即使方法签名没写throws IOException,只要内部throw了未被捕获的IOException(且不是RuntimeException子类),编译就会报错。
立即学习“Java免费学习笔记(深入)”;
- 检查编译错误时,重点看
throw位置所在的最近一层try-catch是否覆盖了该异常类型 - 接口方法声明
throws Exception,实现类可以缩小范围(如只throws IOException),但不能扩大(如加throws SQLException却没在throws里声明) - lambda表达式里抛受检异常,需用自定义函数式接口或包装成
RuntimeException,因为标准接口如Function没声明throws
嵌套异常中getCause()和initCause()的使用场景
当需要把底层异常作为原因包装进新异常时,getCause()用于读取,initCause()用于设置——但多数时候应优先用带cause参数的构造函数,更简洁且线程安全。
initCause()仅在异常对象已创建、又无法修改构造过程时才用(比如反射获取的异常实例)。重复调用会抛IllegalStateException。
- 推荐写法:
throw new ServiceException("DB操作异常", originalException); - 避免写法:
ServiceException se = new ServiceException("DB操作异常"); se.initCause(originalException); throw se; - 注意:
Throwable子类若重写了initCause()但没调用super.initCause(),可能导致getCause()返回null
异常被吞掉的典型陷阱:空catch和printStackTrace()
空catch块(即catch(Exception e){})会让异常彻底消失,调用方完全收不到信号;而只调用e.printStackTrace()只是输出到System.err,不中断流程,上游仍认为执行成功。
这两种写法在生产环境等同于掩盖故障,尤其在异步任务、定时任务或过滤器中极易引发隐蔽问题。
- 日志记录必须用
logger.error("xxx", e),而不是e.printStackTrace() - 如果确定要忽略某类异常(如
InterruptedException在资源清理时),至少加明确注释说明理由 - Spring MVC中全局异常处理器(
@ControllerAdvice)可统一拦截,但不能替代业务层对可恢复异常的合理处理
throw再多也没用。








