Java中应直接用throw e重抛异常以保留堆栈轨迹,避免新建异常丢失信息;throws仅声明不传播异常;包装异常优先用带cause构造函数;禁用空catch和printStackTrace()。
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子类),编译就会报错。
throw位置所在的最近一层try-catch是否覆盖了该异常类型throws Exception,实现类可以缩小范围(如只throws IOException),但不能扩大(如加throws SQLException却没在throws里声明)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在资源清理时),至少加明确注释说明理由@ControllerAdvice)可统一拦截,但不能替代业务层对可恢复异常的合理处理throw再多也没用。