抛出一个异常。这个过程十分容易,只要将其抛出就不用理睬了。当然,有些代码必须捕获异常。捕获异常需要做更多规划。

2.1 捕获异常

如果发生了某个异常,但没有在任何地方捕获这个异常,程序就会终止,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。图形用户界面(GUI)程序(包括applet和应用程序)会捕获异常,打印堆栈轨迹消息,然后返回用户界面处理循环(在调试GUI程序时,最好保证控制台窗口可见,并且没有最小化)。

要想捕获一个异常,需要设置try/catch语句块。最简单的try语句块如下所示:

  1. try
  2. {
  3. 代码
  4. }
  5. catch(ExceptionType e)
  6. {
  7. 此类型的处理
  8. }

如果 try 语句块中的任何代码抛出了 catch 子句中指定的一个异常类,那么
1.程序将跳过 try 语句块的其余代码。
2.程序将执行 catch 子句中的处理器代码。
如果 try 语句块中的代码没有抛出任何异常,那么程序将跳过 catch 子句。
image.png
通常,最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去操心这个问题!如果采用这种处理方式,就必须声明这个方法可能会抛出一个 IOException。
image.png
请记住,编译器严格地执行throws说明符。如果调用了一个抛出检查型异常的方法,就必须处理这个异常,或者继续传递这个异常。
哪种方法更好呢?一般经验是,要捕获那些你知道如何处理的异常,而继续传播那些你不知道怎样处理的异常。

2.2 捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。要为每个异常类型使用一个单独的catch子句,如下例所示:
image.png
异常对象可能包含有关异常性质的信息。要想获得这个对象的更多信息,可以尝试使用
e.getMessage()
得到详细的错误消息(如果有的话),或者使用e.getClass().getName()得到异常对象的实际类型。

2.3 再次抛出异常与异常链

可以在catch子句中抛出一个异常。通常,希望改变异常的类型时会这样做。如果开发了一个供其他程序员使用的子系统,可以使用一个指示子系统故障的异常类型,这很有道理。ServletException就是这样一个异常类型的例子。执行一个servlet 的代码可能不想知道发生错误的细节原因,但希望明确地知道servlet是否有问题。
可以如下捕获异常并将它再次抛出:
image.png
在这里,构造ServleException时提供了异常的消息文本。
不过,可以有-种更好的处理方法,可以把原始异常设置为新异常的“原因”:
image.png
捕获到这个异常时,可以使用下面这条语句获取原始异常:
Throwable original = caughtException.getCause();
强烈建议使用这种包装技术。这样可以在子系统中抛出高层异常,而不会丢失原始异常的细节。
提示:如果在一个方法中发生了一个检查型异常,但这个方法不允许抛出检查型异常,那么包装技术也很有用。我们可以捕获这个检查型异常,并将它包装成一个运行时异常。

有时你可能只想记录一个异常,再将它重新抛出,而不做任何改变:
image.png
在Java7之前,这种方法存在一个问题。假设这个代码在以下方法中:
public void updateRecord( ) throws SQLException
Java 编译器查看 catch 块中的 throw 语句,然后查看 e 的类型,会指出这个方法可以抛出任何Exception而不只是SQLException。现在这个问题已经得到改进。编译器会跟踪到e来自try块。假设这个try块中仅有的检查型异常是SQLException实例,另外,假设e在catch块中未改变,将外围方法声明为throws SQLException就是合法的。

2.4 finally 子句

代码抛出一个异常时,就会停止处理这个方法中剩余的代码,并退出这个方法。如果这个方法已经获得了只有它自己知道一些本地资源,而且这些资源必须清理,这就会有问题。一种解决方案是捕获所有异常,完成资源的清理,再重新抛出异常。但是,这种解决方案比较烦琐,这是因为需要在两个地方清理资源分配。一个在正常的代码中;另一个在异常代码中。finally子句可以解决这个问题。

注释: 在Java 7之后,还有一种更精巧的解决方案,即try-with-resources语句下一节将介绍这个内容)。我们之所以要详细讨论finally机制,是因为这是概念基础。不过在实际中,try-with-resources语句可能比finally子句更常用。

不管是否有异常被捕获,finally 子句中的代码都会执行。例如对输入流的关闭,finally 子句主要用于清理资源。
image.png
内层的try语句块只有一个职责,就是确保关闭输人流。外层的try语句块也只有一个职责,就是确保报告出现的错误。这种解决方案不仅清楚,而且功能更强:将会报告 finally子句中出现的错误。

2.5 try-with-resources 语句

try-with-resources 语句(带资源的try语句)的最简形式为:
image.png
try 块退出时,会自动调用 res.close()。下面给出一个典型的例子,这里要读取一个文件中的所有单词:
image.png
这个块正常退出时,或者存在一个异常时,都会调用 in.close() 方法,就好像使用了 finally 块一样。
还可以指定多个资源。例如:
image.png
不论这个块如何退出,in 和 out 都会关闭。如果你用常规方式手动编程,就需要两个嵌套的 try/finally 语句。
在 Java 9 中,可以在 try 首部中提供之前声明的事实最终变量:
image.png
如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。try-with-resources语句可以很好地处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由 addSuppressed 方法增加到原来的异常。如果对这些异常感兴趣,可以调用 getSuppressed 方法,它会生成从 close 方法抛出并被抑制的异常数组。
你肯定不想采用这种常规方式编程。只要需要关闭资源,就要尽可能使用try-with-resources语句。

注释:try-with-resources语句自身也可以有catch子句,甚至还可以有一个 finally子句。这些子句会在关闭资源之后执行。

2.6 分析堆栈轨迹元素