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块可以无须在异常处理块中使用ifswitch判断异常类型,但依然可以针对不同的异常类型提供相应的处理逻辑,从而提供更细致、更有条理的异常处理逻辑。

出现异常时只会执行多个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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class DivTest
{
public static void main(String[] args)
{
try
{
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int c = a / b;
System.out.println("您输入的两个数相除的结果是:" + c );
}
catch (IndexOutOfBoundsException ie)
{
System.out.println("数组越界:运行程序时输入的参数个数不够");
}
catch (NumberFormatException ne)
{
System.out.println("数字格式异常:程序只能接受整数参数");
}
catch (ArithmeticException ae)
{
System.out.println("算术异常");
}
catch (Exception e)
{
System.out.println("未知异常");
}
}
}

上面程序针对IndexOutOfBoundsExceptionNumberFormatExceptionArithmeticException类型的异常,提供了专门的异常处理逻辑。Java运行时的异常处理逻辑可能有如下几种情形。

  • 如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常。
  • 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,Java运行时将调用NumberFormatException对应的catch块处理该异常。
  • 如果运行该程序时输入的第二个参数是0,将发生除0异常,Java运行时将调用ArithmeticException对应的catch块处理该异常。
  • 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。

上面程序中的三种异常,都是非常常见的运行时异常,读者应该记住这些异常,并掌握在哪些情况下可能出现这些异常。

常见运行时异常

1
2
3
IndexOutOfBoundsException
NumberFormatException
ArithmeticException

NullPointerException

什么时候会引发NullPointerException异常

当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;

public class NullTest
{
public static void main(String[] args)
{
Date d = null;
try
{
System.out.println(d.after(new Date()));
}
catch (NullPointerException ne)
{
System.out.println("空指针异常");
}
catch(Exception e)
{
System.out.println("未知异常");
}
}
}

上面程序针对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
2
3
4
5
6
7
8
9
10
11
12
13
14
try
{
...
}
catch(RuntimeException e)
{
//1
System.out.println("运行时异常");
}
catch(NullPointerException e)
{
//②
System.out.println("空指针异常");
}

上面代码中有两个catch块,前一个catch块捕获RuntimeException异常,后一个catch块捕获NullPointerException异常,编译上面代码时将会在②处出现已捕获到异常java.lang.NullPointerException的错误,因为①处的RuntimeException已经包括了NullPointerException异常,所以②处的catch块永远也不会获得执行的机会。

注意
异常捕获时,一定要记住先捕获小异常,再捕获大异常。