1. Java 中的异常分类
Java中的异常类均以Throwable
为父类,而Throwable
又派生出 Error
和 Exception
两类,区别如下
1.1 Error类及其子类
代表了JVM自身的异常。这一类异常发生时,无法通过程序来修正。例如系统崩溃、内存溢出等。与异常不同,错误表示程序无法继续执行下去,一般不需要进行捕获或处理。错误通常是由底层系统或环境导致的,它们是不可控的,最可靠的方式就是尽快地停止JVM的运行。
1.2 Exception类及其子类
Exception又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为非受检异常(Unchecked Exception)和受检异常(Checked Exception),其中Error类及其子类也是非受检异常。
受检异常:也称为“编译时异常”,编译器在编译期间检查的那些异常。由于编译器“检查”这些异常以确保它们得到处理,因此称为“检查异常”。如果抛出检查异常,那么编译器会报错,需要开发人员手动处理该异常,要么捕获,要么重新抛出。除了RuntimeException之外,所有直接继承 Exception 的异常都是检查异常。
非受检异常:也称为“运行时异常”,编译器不会检查运行时异常,在抛出运行时异常时编译器不会报错,当运行程序的时候才可能抛出该异常。Error及其子类和RuntimeException 及其子类都是非检查异常。
Java 中异常类的关系可以使用如下 UML 类图表示
受检异常和非受检异常是针对编译器而言的,是编译器来检查该异常是否强制开发人员处理该异常:
受检异常导致异常在方法调用链上显式传递,而且一旦底层接口的检查异常声明发生变化,会导致整个调用链代码更改。
使用非受检异常不会影响方法签名,而且调用方可以自由决定何时何地捕获和处理异常。
1.3 受检异常举例
编译器提示需要处理这个异常,这种异常处理有两种方式:
- 在方法签名上抛出此异常,由方法调用方处理
- 使用try-catch 捕获异常,内部处理
1.4 非受检异常异常举例
所有继承 RuntimeException 的异常都是非检查异常,直接抛出非检查异常编译器不会提示错误
方法直接抛出 RuntimeException 时,编译器并不会要求捕获或者抛出此异常。
2. try-catch
try-catch
关键字在Java 中主要用于捕获异常,并进行处理。简单示例如下:
在 try{} 代码块中,是可能抛出异常的代码
或者调用了签名上会抛出异常的方法
。cath{} 代码块中则是捕获异常,并处理异常。注意:catch 可以捕获多种异常,并根据异常种类不同,分开处理,但是要注意异常捕获的顺序。
在上面的示例中,先捕获了 IOException,IDE 就会提示下面的 FileNotFoundException 无需再被捕获了,因为 IOException 是 FileNotFoundException 的父类,捕获到 IOException 之后,其所有子类的异常捕获代码都会失效。
下面演示如何同时捕获多个异常,并用同一个分支处理:
当我们需要对多个异常分组处理时,可以使用 catch(Exception1 | Exception2 e) 来捕获多个异常。
3. try-catch-finally
try-catch-finally
用于在处理异常时,不管是否发生异常,都要执行的操作。示例代码如下:
try 代码块中发生了异常:
提问:为什么先打印了 finally 代码块中的内容,后打印了异常信息?
try 代码块未异常:
finally{} 一般用于资源的关闭,或者数据的清理, 但是也可以在 finally 中执行 return 命令来修改方法返回。示例代码如下:
提问:大家觉得这个cal 方法返回值是多少?为什么?
正常情况下,finally 代码块中的代码一定是会执行的,但是也有以下几种失效情况:
在执行 try 或 catch 块之前 JVM 被非法终止,比如程序正在运行,但是使用 pkill -9 java 命令强行停止 Java 进程。
在 try 或 catch 块中发生了 System.exit() 调用,导致 JVM 直接退出。
在 try 或 catch 块中发生了死循环,导致程序无法继续执行。
在 try 或 catch 块中发生了栈溢出异常(StackOverflowError)或虚拟机异常(如 OutOfMemoryError),导致 JVM 崩溃。
程序所在的线程被强制中断或程序进程被操作系统杀死。
在 try 或 catch 块中使用了 System.halt() 方法,显式终止 JVM。
调用了 native 方法,而该方法中不包含 finally 块。
4. try-with-resources 用法
try-with-resources
是 Java 7 引入的一个语法结构,用于更加方便地处理需要关闭的资源。它可以自动关闭实现了 AutoCloseable
或 Closeable
接口的资源,无需手动编写 finally 块来关闭资源。try-with-resources 的语法形式是在 try 关键字之后使用圆括号括起来的资源声明列表。每个资源在括号中声明并初始化。当 try 块结束时,无论是否发生异常,这些资源都将自动关闭,而不需要显式调用 close()
方法。以下是一个读取文件并自动关闭流的示例:
FileInputStream 之所以可以自动关闭,是因为其继承了 InputStream 类,而InputStream类实现了 Closeable 接口,FileInputStream重写了 close()方法,以下是具体实现:
那如何证明使用 try-with-resources 时,close 方法真的被调用了呢?我们可以使用如下命令编译 App.java 文件,并看下生成的字节码文件
1 | -g 参数用于生成与调试相关的信息,包括调试符号和源代码行号。它允许在编译后的字节码中插入调试信息,以便在调试过程中可以精确地映射回源代码的行号和变量名 |
生成的 class 文件如下:
从上面的 class 文件中我们可以清楚看到 jvm 帮我们生成了一个 catch 代码块,用来捕获外层 try 代码块可能抛出的异常,并且在 catch 代码块中显式调用了 fis 的 close() 方法进行资源关闭。这就是为什么说 无论是否发生异常,这些资源都将自动关闭
。
5. 异常处理规范
异常处理规范参考 《阿里巴巴代码开发规范》 中的约束。