受检异常必须处理但不必捕获,关键看当前层是否具备处理能力:能fallback、修复、清理或屏蔽细节时才捕获;仅记录再抛出、转为非受检异常或空catch均属错误;重抛需保留原始异常上下文。

受检异常必须处理,这是 Java 编译器的硬性要求——不 try-catch,就得在方法签名中 throws,否则编译直接失败。但“必须处理”不等于“一律捕获”,关键在于判断当前层是否具备处理能力与职责。
该捕获时:能做有意义的响应
只有当方法上下文可以执行具体恢复动作,捕获才有价值:
- 提供 fallback:如读配置失败时加载默认值,网络请求失败时返回缓存数据
- 局部修复:文件被占用时自动切换临时目录,连接超时后自动重试一次
- 资源清理:用
try-with-resources确保流、连接等及时关闭,避免泄漏 - 屏蔽细节:UI 层统一转为用户友好的提示,不暴露底层
FileNotFoundException
不该捕获时:仅记录再抛出是典型误区
以下做法看似“处理了”,实则违背原则,削弱异常传播链:
-
catch (IOException e) { logger.error("读文件出错", e); throw e; }—— 日志+原样 rethrow,没增加任何业务逻辑,不如直接throws -
catch (SQLException e) { throw new RuntimeException(e); }—— 包装成非受检异常绕过编译检查,调用方失去处理机会 - 空
catch块或只写e.printStackTrace()—— 异常信号丢失,问题难以定位
重新抛出要讲方法
若需包装后向上抛,必须保留原始异常上下文:
- 用带 cause 构造器:例如
throw new ServiceException("数据库操作失败", originalException); - 避免信息断层:堆栈跟踪应同时包含当前层和原始异常,方便逐层排查
- 不要混用记录与抛出:要么记日志后静默处理(已真正兜底),要么抛出交由上层决定,二者并行会导致重复日志干扰分析
记录本身不是目的,明确责任才是核心
受检异常的设计本意是让调用方意识到“这事可能出问题,你得想好怎么应对”。throws 不是甩锅,而是声明契约;try-catch 不是保险,而是承担恢复责任。方法签名里出现 throws IOException,就是在告诉调用者:“我干这件事依赖外部资源,出问题你要负责。”











