在Java中运行时异常和受检异常怎么区分_Java异常分类解析

运行时异常指RuntimeException及其子类,编译期不强制处理,反映逻辑错误;受检异常是Exception的其他子类,必须声明或捕获;Error不属于异常,不应捕获。

运行时异常就是 RuntimeException 及其子类

Java 中所有异常都继承自 Throwable,而运行时异常特指 RuntimeException 类及其直接或间接子类,比如 NullPointerException

ArrayIndexOutOfBoundsExceptionIllegalArgumentException。这类异常在编译期不强制要求处理——你写 list.get(100) 即使 list 只有 2 个元素,编译器也放行。

它们通常反映程序逻辑错误,比如空指针、越界、类型转换失败,应该靠代码审查和测试提前暴露,而不是靠 try-catch 到处兜底。

  • 常见误区:以为 throw new RuntimeException("xxx") 是“随便抛”,其实它和 NullPointerException 一样属于运行时异常,调用方完全可不捕获
  • 设计原则:自定义业务异常如果不想强制调用方处理,就继承 RuntimeException
  • 注意:RuntimeException 本身是 Exception 的子类,但它被 JVM 特殊对待——编译器跳过检查

受检异常必须显式声明或捕获

RuntimeException 及其子类外,所有继承自 Exception 但不继承自 RuntimeException 的异常,都是受检异常(checked exception),典型如 IOExceptionSQLExceptionClassNotFoundException

编译器会强制你面对它们:要么在方法签名中用 throws 声明,要么用 try-catch 包裹。否则编译直接报错:Unhandled exception type XXX

  • 关键判断依据:看异常类是否在 java.lang 包里且是 RuntimeException 的后代;不在 java.lang 或不是其后代,基本就是受检异常
  • 常见陷阱:误把 InterruptedException 当成可忽略的运行时异常——它其实是受检异常,必须处理,否则线程中断信号会被静默吞掉
  • 性能无关:是否受检和性能无关,只和编译期约束有关;JVM 运行时对两类异常的处理机制完全一致

Throwable 的完整继承结构不能只记两层

很多人只记“Exception 分运行时/受检”,但忽略了 Error 也是 Throwable 的直系子类。像 OutOfMemoryErrorStackOverflowError 都属于 Error,它们既不是运行时异常,也不是受检异常——编译器同样不检查,但原则上不应被捕获或处理。

Throwable
├── Error          // 不该捕获,如 OutOfMemoryError
├── Exception
│   ├── RuntimeException   // 运行时异常,不强制处理
│   └── 其他 Exception     // 受检异常,必须声明或捕获
  • 错误做法:写 catch (Exception e) 试图兜住一切,结果连 OutOfMemoryError 都被拦下,掩盖了真正的问题根源
  • 正确姿势:按需捕获具体异常类型,比如只 catch (IOException e),避免宽泛捕获
  • 注意:ExceptionError 都继承自 Throwable,所以 catch (Throwable t) 理论上能抓到所有,但生产环境严禁这么写

自定义异常时选错父类会导致调用方困惑

如果你写了一个 InvalidOrderException,却让它继承 Exception,那所有调用它的方法都得加 throwstry-catch;如果它继承 RuntimeException,调用方就完全自由。这个选择不是技术问题,而是 API 设计契约。

  • 业务异常一般选 RuntimeException:比如参数校验失败、状态不合法,属于调用方应修正的逻辑问题
  • 外部依赖异常倾向受检:比如调用支付接口返回 HTTP 500,你封装成 PaymentNetworkException extends Exception,提醒调用方考虑重试或降级
  • 容易忽略的点:IDE 自动生成 throws 时可能把本该是运行时的异常也加进去,要手动删掉;反之,如果漏了 throws 导致编译失败,别急着改成运行时异常,先确认是否真该由调用方响应