总结自:《Java 核心技术第 10 版》
下图是 Java 异常层次结构图:
所有的异常都是由 Throwable 继承而来(注意 Throwable 是类而不是接口),Error 和 Exception 是 Throwable 的直接子类。
Error 类用于描述 Java 运行时系统的内部错误和资源耗尽错误(比如 OOM)。应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。这种情况很少出现。
在编写 Java 程序时,需要关注 Exception 层次结构。这个层次结构又分解为两个分支:一个分支派生于 RuntimeException;另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException;而程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于其他异常。
派生于 RuntimeException 的异常包含下面几种情况:
- 错误的类型转换
- 数组访问越界
- 访问 null 指针
不是派生于 RuntimeException 的异常包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在
“如果出现 RuntimeException 异常,那么就一定是你的问题”[1]是一条相当有道理的规则。应该通过检测数组下标是否越界来避免 ArrayIndexOutOfBoundsException 异常;应该通过在使用变量之前检测是否为 null 来杜绝 NullPointerException 异常的发生。
但即使先检查文件是否存在再尝试打开文件,这个文件也还是有可能在你检查完它是否存在之后被删除导致出错。因此,“是否存在”取决于环境,而不只是取决于你的代码。所以“找不到文件”这类异常属于其他异常。
Java 语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。编译器会检查是否为受查异常提供了异常处理器,没有则编译不通过。也就是说,方法抛出 RuntimeException 类及其子类的对象时,编译器允许程序不做任何处理——可以既不在方法中添加 throws 语句,也可以不用 try-catch 来处理;而对于继承自 Exception 及其子类的其他异常,编译器则会要求必须用 throws 或者 try-catch 语句来处理。
另:RuntimeException 这个名字很容易让人混淆。实际上,现在讨论的所有错误都发生在运行时。
参考:Java 中的异常体系、《Java 入门 1·2·3》
在实际开发过程中,也会在程序校验不通过时主动抛 RuntimeException(比如用 IllegalArgumentException 表示传参错误)给全局异常处理器处理,这种情况下算不上程序有问题。 ↩︎