受检异常继承Exception但不属RuntimeException体系,编译器强制处理;非受检异常继承RuntimeException或Error,编译器不强制。
怎么一眼判断一个异常是受检还是非受检
看它继承谁:RuntimeException 或 Error 的子类 → 非受检;其他 Exception 子类(且不属 RuntimeException 体系)→ 受检。比如 IOException 继承 Exception,没碰 RuntimeException,编译器就拦你;而 NullPointerException 是 RuntimeException 的直系后代,写完直接能编译通过。
为什么编译器只强制处理受检异常
这不是 JVM 的硬性要求,而是 Java 语言层面的设计约定——编译器(javac)在语法检查阶段主动介入,对非 RuntimeException/非 Error 的 Exception 子类做“必须处理”校验。JVM 运行时其实根本不区分这两类,字节码里抛异常的指令都一样。
- 你漏处理
SQLException?javac报错:unreported exception SQLException; must be caught or declared to be thrown - 你漏处理
IllegalArgumentException?编译器沉默,运行到那儿才崩 - 这个机制本质是“提醒你:这事外部可能出问题,别假装看不见”
实际写代码时最容易踩的坑
把受检异常塞进 lambda、Stream 或 CompletableFuture 里,结果编译不过——因为这些 API 的函数式接口(如 Function、Supplier)方法签名没声明抛出受检异常。
- 错误写法:
list.stream().map(s -> new FileInputStream(s))→FileInputStream构造会抛FileNotFoundException(受检),但Function不允许 - 正确解法:要么用工具类包装成非受检异常(如
throw new RuntimeException(e)),要么改用支持受检异常的第三方接口(如ThrowingFunction) - 更隐蔽的坑:在 Spring 的
@Transactional方法里吞掉受检异常却不抛出,事务可能不会按预期回滚(因为 Spring 默认只对RuntimeException和Error回滚)
自定义异常该选哪一类
取决于调用方有没有能力/责任去恢复它: