10.2.2 异常类的继承体系
10.2.2 异常类的继承体系
当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理该异常;如果不是,则再次拿该异常对象和下一个catch块里的异常类进行比较。Java异常捕获流程示意图如图10.1所示。

当程序进入负责异常处理的catch块时,系统生成的异常对象ex将会传给catch块后的异常形参,从而允许catch块通过该对象来获得异常的详细信息。
从图10.1中可以看出,try块后可以有多个catch块,这是为了针对不同的异常类提供不同的异常处理方式。当系统发生不同的意外情况时,系统会生成不同的异常对象,Java运行时就会根据该异常对象所属的异常类来决定使用哪个catch块来处理该异常。
通过在try块后提供多个catch块可以无须在异常处理块中使用if、switch判断异常类型,但依然可以针对不同的异常类型提供相应的处理逻辑,从而提供更细致、更有条理的异常处理逻辑。
出现异常时只会执行多个catch块中的一个
从图10.1中可以看出,在通常情况下,如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块被执行。除非在循环中使用了continue开始下一次循环,下一次循环又重新运行了try块,这才可能导致多个catch块被执行,但对应其中一次循环还是只会有一个catch块被执行。
不可省略try块和catch块后面的花括号
try块与if语句不一样,try块后的花括号{...}不可以省略,即使try块里只有一行代码,也不可省略这个花括号。与之类似的是, catch块后的花括号({...})也不可以省略。
try块中声明的变量只在该try块中有效
try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问该变量。
Java提供了丰富的异常类,这些异常类之间有严格的继承关系,图10.2显示了Java常见的异常类:

从图10.2中可以看出,Java把所有的非正常情况分成两种:异常( Exception)和错误(Error),它们都继承Throwable父类。
错误
错误Error,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。下面看几个简单的异常捕获例子。
1 | public class DivTest |
上面程序针对IndexOutOfBoundsException、 NumberFormatException、 ArithmeticException类型的异常,提供了专门的异常处理逻辑。Java运行时的异常处理逻辑可能有如下几种情形。
- 如果运行该程序时输入的参数不够,将会发生数组越界异常,
Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常。 - 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,
Java运行时将调用NumberFormatException对应的catch块处理该异常。 - 如果运行该程序时输入的第二个参数是0,将发生除0异常,
Java运行时将调用ArithmeticException对应的catch块处理该异常。 - 如果程序运行时出现其他异常,该异常对象总是
Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。
上面程序中的三种异常,都是非常常见的运行时异常,读者应该记住这些异常,并掌握在哪些情况下可能出现这些异常。
常见运行时异常
1 | IndexOutOfBoundsException |
NullPointerException
什么时候会引发NullPointerException异常
当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常。
1 | import java.util.*; |
上面程序针对NullPointerException异常提供了专门的异常处理块。上面程序调用一个null对象的after()方法,这将引发NullPointerException异常,Java运行时将会调用NullPointerException对应的catch块来处理该异常;如果程序遇到其他异常,Java运行时将会调用最后的catch块来处理异常。
先捕获小异常 再捕获大异常
正如在前面程序所看到的,程序总是把对应Exception类的catch块放在最后,这是为什么呢?想下图10.1所示的Java异常捕获流程,读者可能明白原因:
如果把Exception类对应的catch块排在其他catch块的前面,Java运行时将直接进入该catch块(因为所有的异常对象都是Exception或其子类的实例),而排在它后面的catch块将永远也不会获得执行的机会。
实际上,进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面(简称:先处理小异常,再处理大异常),否则将出现编译错误。看如下代码片段:
1 | try |
上面代码中有两个catch块,前一个catch块捕获RuntimeException异常,后一个catch块捕获NullPointerException异常,编译上面代码时将会在 代码2 处出现已捕获到异常java.lang.NullPointerException的错误,因为 代码1 处的RuntimeException已经包括了NullPointerException异常,所以 代码2 处的catch块永远也不会获得执行的机会。
注意
异常捕获时,一定要记住先捕获小异常,再捕获大异常。