核心是编译器是否强制处理:受检异常继承Exception但不经过RuntimeException(如IOException),编译报错;非受检异常可追溯至RuntimeException或Error(如NullPointerException、OutOfMemoryError),编译通过。

区分受检异常和非受检异常,核心就一条:看编译器会不会拦你。
看继承关系最直接
所有异常都继承自 Throwable,往下分两支:
-
受检异常:继承自 Exception,但不经过 RuntimeException(比如
IOException、SQLException、ClassNotFoundException) -
非受检异常:只要往上追溯能碰到 RuntimeException 或 Error(比如
NullPointerException、IllegalArgumentException、OutOfMemoryError)
注意:Exception 本身不是受检异常,它的子类才是;RuntimeException 是 Exception 的子类,但被 Java 特别划为“非受检”分支。
编译器行为是判断依据
不用背例子,现场验证最快:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 写
throw new IOException();,不加try-catch也不写throws→ 编译报错:“unreported exception”,这就是受检异常 - 写
throw new NullPointerException();,同样不处理 → 编译通过,运行时才可能抛出,这就是非受检异常
这个差异来自 javac,不是 JVM 运行时机制。JVM 对所有异常一视同仁,只是编译器对某些类型“多问一句”。
按设计意图选类型
自定义异常时,继承谁决定了它是受检还是非受检:
- 想让调用方必须应对(如支付失败需重试、文件缺失需提示用户)→ 继承 Exception
- 想表达逻辑错误或非法状态(如参数为空、状态不匹配)→ 继承 RuntimeException
别把 IOException 包装成 RuntimeException 就为了省 try-catch——这等于把“可恢复的外部问题”当成“不可救的 bug”来对待。
常见陷阱要避开
实际编码中容易踩坑的地方:
- Lambda 表达式里不能直接
throw受检异常(因为函数式接口没声明throws),得包装成非受检异常或用工具类如ThrowingFunction -
@Transactional方法里捕获了受检异常却没重新抛出 → Spring 默认不回滚事务,需显式配置rollbackFor = Exception.class - 误以为
Exception是受检异常 → 实际上RuntimeException也是Exception

















