5.1 异常捕获与处理
Java使用异常处理机制为程序提供了错误处理的能力。在编写异常处理程序时经常会使用3个异常处理程序组件:try、catch和finally。在Java 7中引入了try-with-resources语句,方便处理Closeable资源(例如流)的异常处理。
5.1.1 先从一个例子入手
为了更好地理解异常处理机制,我们定义了名为ListOfNumbers的类。在构造时,ListOfNumbers会创建一个ArrayList,其中包含10个序列值为0到9的整数元素。ListOfNumbers类还定义了一个名为writeList的方法,该方法将数列表写入一个名为OutFile.txt的文本文件中。此示例使用在java.io中定义的输出类,这些类包含在基本I/O中。
构造函数FileWriter初始化文件上的输出流。如果文件无法打开,构造函数会抛出一个IOException异常。第二个对ArrayList类的get方法的调用,如果其参数的值太小(小于0)或太大(超过ArrayList当前包含的元素数量),就将抛出IndexOutOfBoundsException。
如果尝试编译ListOfNumbers类,那么编译器将打印有关FileWriter构造函数抛出的异常错误消息。但是,它不显示有关get抛出的异常错误消息。原因是构造函数IOException抛出的异常是一个检查异常,而get方法IndexOutOfBoundsException抛出的异常是未检查的异常。如图5-1所示,在IDE中,也仅仅会对检查异常做提示。
图5-1 IDE对检查异常的提示
现在,我们已经熟悉了ListOfNumbers类,并且知道了其中哪些地方可能抛出异常。下一步我们就可以编写异常处理程序来捕获和处理这些异常了。
5.1.2 try块
构造异常处理程序的第一步是封装可能在try块中抛出异常的代码。一般来说,try块看起来像下面这样:
示例标记code中的代码段可以包含一个或多个可能抛出的异常。
每行可能抛出异常的代码都可以用单独的一个try块,或者多个异常放置在一个try块中。以下示例非常简短,因此只使用一个try块。
如果在try块中发生异常,那么该异常将由与其相关联的异常处理程序进行处理。要将异常处理程序与try块关联,必须在其后面放置一个catch块。
5.1.3 catch块
通过在try块之后直接提供一个或多个catch块,可以将异常处理程序与try块关联。在try块的结尾和第一个catch块的开始之间没有代码。
每个catch块是一个异常处理程序,处理由其参数指示的异常类型。参数类型ExceptionType声明了处理程序可以处理的异常类型,并且必须是从Throwable类继承的类的名称。处理程序可以使用名称引用异常。
catch块包含了在调用异常处理程序时执行的代码。当处理程序是调用堆栈中第一个与ExceptionType匹配的异常抛出的类型时,运行时系统将调用异常处理程序。若抛出的对象可以合法地分配给异常处理程序的参数,则系统认为它是匹配的。
以下是writeList方法的两个异常处理程序:
异常处理程序可以做的不仅仅是打印错误消息或停止程序。它们可以执行错误恢复,提示用户做出决定,或者使用异常链将错误传播到更高级别的处理程序,如“异常链”部分所述。
5.1.4 在一个异常处理程序中处理多个类型的异常
在Java 7和更高版本中,单个catch块可以处理多种类型的异常。此功能可以减少代码重复,并减少定义过于宽泛的异常。
在catch子句中,多个类型的异常使用竖线(|)分隔每个异常类型:
注意
如果catch块处理多个异常类型,那么catch参数将隐式为final。在本示例中,catch参数ex是final,因此不能在catch块中为其分配任何值。
5.1.5 finally块
finally块总是在try块退出时执行。这确保即使发生意外异常也会执行finally块。finally的用处不仅仅是异常处理,它还允许程序员避免清理代码意外绕过return、continue或break。因此,将清理代码放在finally块中是一个好的做法,即使没有预期的异常。
注意
如果在执行try或catch代码时JVM退出,则finally块可能无法执行。同样,如果执行try或catch代码的线程被中断或杀死,则finally块可能不执行,即使应用程序作为一个整体继续。
writeList方法的try块打开一个PrintWriter。程序应该在退出writeList方法之前关闭该流。这提出了一个有点复杂的问题,因为writeList的try块可以以3种方式中的一种退出:
· new FileWriter语句失败并抛出IOException。
· list.get(i)语句失败并抛出IndexOutOfBoundsException。
· 一切成功,try块正常退出。
运行时系统总是执行finally块内的语句,而不管try块内发生了什么,所以它是执行清理的完美场所。
下面的finally块为writeList方法清理,然后关闭PrintWriter。
finally块是防止资源泄漏的关键工具。当关闭文件或恢复资源时,将代码放在finally块中,以确保资源始终恢复。Java 7中提供了try-with-resources语句,当不再需要时能够自动释放系统资源。
5.1.6 try-with-resources语句
try-with-resources是Java 7中一个新的异常处理机制,能够很容易地关闭在try-catch语句块中使用的资源。资源(resource)是指在程序完成后必须关闭的对象。try-with-resources语句确保了每个资源在语句结束时关闭。所有实现了java.lang.AutoCloseable接口的对象(其中包括实现了java.io.Closeable的所有对象),都可以被自动关闭。
例如,我们自定义一个资源类:
执行输出如下:
do something resource is closed
资源被自动关闭。
再来看一个例子,可以同时关闭多个资源:
最终输出为:
do something do other things other resource is closed some resource is closed
在try语句中越是最后使用的资源越早被关闭。
try-with-resource语句在Java 9中已经做了改进,简化了使用。