所谓异常(exception)是在程序运行过程中产生的使程序终止正常运行的错误对象,如数组下标越界、整数除法中零作除数、文件找不到等都可能使程序终止运行。
Java语言规定当某个对象的引用为null时,调用该对象的方法或使用对象时就会产生NullPointerException异常。
Java语言的异常处理采用面向对象的方法,定义了多种异常类。Java异常类都是Throwable类的子类,是Object类的直接子类,定义在java.lang包中。Throwable类有两个子类,一个是Error类,另一个是Exception类,这两个子类又分别有若干个子类。
非检查异常(unchecked exception)是RuntimeException类及其子类异常,也称为运行时异常。常见的非检查异常如图12-2所示。
非检查异常是在程序运行时检测到的,可能发生在程序的任何地方且数量较大,因此编译器不对非检查异常(包括Error类的子类)处理,这种异常又称免检异常。
尽管编译器不对非检查异常处理,但程序运行时产生这类异常,程序也不能正常结束。为了保证程序正常运行,要么避免产生非检查异常,要么对非检查异常进行处理。
检查异常(checked exception)是除RuntimeException类及其子类以外的异常类,有时也称为必检异常。对这类异常,程序必须捕获或声明抛出,否则编译不能通过。程序12.1中的read()方法声明抛出IOException异常就是必检异常。例如,若试图使用Java命令运行一个不存在的类,则会产生ClassNotFoundException异常,若调用了一个不存在的方法,则会产生NoSuchMethodException异常。
异常处理可分为下面几种:使用try-catch-finally捕获并处理异常;通过throws子句声明抛出异常;用throw语句抛出异常;使用try-with-resources管理资源。
异常都是在方法中产生的。方法运行过程中如果产生了异常,在这个方法中就生成一个代表该异常类的对象,并把它交给系统,运行时系统寻找相应的代码来处理该异常。这个过程称为抛出异常。运行时系统在方法的调用栈中查找,从产生异常的方法开始进行回溯,直到找到包含相应异常处理的方法为止,这一过程称为捕获异常。
当try块中产生异常,运行时系统从上到下依次检测异常对象与哪个catch块声明的异常类相匹配,若找到匹配的或其父类异常,就进入相应catch块处理异常,catch块执行完毕说明异常得到处理。
(3)finally块是可选项。异常的产生往往会中断应用程序的执行,而在异常产生前,可能有些资源未被释放。有时无论程序是否发生异常,都要执行一段代码,这时就可以通过finally块实现。无论异常产生与否finally块都会被执行。即使是使用了return语句,finally块也要被执行,除非catch块中调用了System.exit()方法终止程序的运行。
另外需要注意,一个try块必须有一个catch块或finally块,catch块或finally块也不能单独使用,必须与try块搭配使用。
在异常类的根类Throwable中还定义了其他方法,如下所示:
public void printStackTrace():在标准错误输出流上输出异常调用栈的轨迹。
public String getMessage():返回异常对象的细节描述。
public void printStackTrace(PrintWriter s):在指定输出流上输出异常调用栈的轨迹。
public String toString():返回异常对象的简短描述,是Object类中同名方法的覆盖。这些方法被异常子类所继承,可以调用异常对象的方法获得异常的有关信息,方便程序调试。有关其他方法的详细内容,请参阅Java API文档。
如前所述,一个try语句后面可以跟两个或多个catch语句。虽然每个catch语句经常提供自己特有的代码序列,但是有时捕获异常的两个或多个catch语句可能执行相同的代码序列。现在可以使用JDK 7提供的一个新功能,用一个catch语句处理多个异常,而不必单独捕获每个异常类型,减少了代码重复。
程序运行当尝试除以0时,将产生一个ArithmeticException错误。当尝试越界访问letter数组时,将产生一个ArrayIndexOutOfException错误,两个异常被同一个catch语句捕获。注意,多重捕获的每个形参隐含地为final,所以不能为其赋新值。
所有的异常都产生在方法(包括构造方法)内部的语句。有时方法中产生的异常不需要在该方法中处理,可能需要由该方法的调用方法处理,这时可以在声明方法时用throws子句声明抛出异常,将异常传递给调用该方法的方法处理。
注意:对于运行时异常可以不做处理,对于非运行时异常必须使用try-catch结构捕获或声明方法抛出异常。
3.7try-with-resources语句
Java程序中经常需要创建一些对象(如I/O流、数据库连接),这些对象在使用后需要关闭。忘记关闭文件可能导致内存泄露,并引起其他问题。在JDK 7之前,通常使用finally语句来确保一定会调用close()方法。
可以看到,为了关闭连接资源要在finally块中写这些代码,如果在一个try块中打开多个资源,代码会更长。JDK 7提供的自动关闭资源的功能为管理资源(如文件流、数据库连接等)提供了一种更加简便的方式。这种功能是通过一种新的try语句实现的,称为try-with-resources,有时也称为自动资源管理。try-with-resources的主要优点是可以避免在资源(如文件流)不需要时忘记将其关闭。
尽管Java已经预定义了许多异常类,但有时还需要定义自己的异常。编写自定义异常类实际上是继承一个API标准异常类,用新定义的异常处理信息覆盖原有信息的过程。
断言是Java 1.4版新增的一个特性,并在该版本中增加了一个关键字assert。断言功能可以被看成是异常处理的高级形式。所谓断言(assertion)是一个Java语句,其中指定一个布尔表达式,程序员认为在程序执行时该表达式的值应该为true。系统通过计算该布尔表达式执行断言,若该表达式为false,系统会报告一个错误。通过验证断言是true,能够使程序员确信程序的正确性。
断言的使用是一个复杂的问题,因为这将涉及程序的风格、断言运用的目标、程序的性质等。通常,断言用于检查一些关键的值,并且这些值对整个应用程序或局部功能的实现有较大影响,并且当断言失败,这些错误是不容易恢复的。