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
块永远也不会获得执行的机会。
注意
异常捕获时,一定要记住先捕获小异常,再捕获大异常。