ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 9.6 用finally清除 无论一个异常是否在`try`块中发生,我们经常都想执行一些特定的代码。对一些特定的操作,经常都会遇到这种情况,但在恢复内存时一般都不需要(因为垃圾收集器会自动照料一切)。为达到这个目的,可在所有异常控制器的末尾使用一个`finally`从句(注释④)。所以完整的异常控制小节象下面这个样子: ``` try { // 要保卫的区域: // 可能“抛”出A,B,或C的危险情况 } catch (A a1) { // 控制器 A } catch (B b1) { // 控制器 B } catch (C c1) { // 控制器 C } finally { // 每次都会发生的情况 } ``` ④:C++异常控制未提供`finally`从句,因为它依赖构造器来达到这种清除效果。 为演示`finally`从句,请试验下面这个程序: ``` //: FinallyWorks.java // The finally clause is always executed public class FinallyWorks { static int count = 0; public static void main(String[] args) { while(true) { try { // post-increment is zero first time: if(count++ == 0) throw new Exception(); System.out.println("No exception"); } catch(Exception e) { System.out.println("Exception thrown"); } finally { System.out.println("in finally clause"); if(count == 2) break; // out of "while" } } } } ///:~ ``` 通过该程序,我们亦可知道如何应付Java异常(类似C++的异常)不允许我们恢复至异常产生地方的这一事实。若将自己的`try`块置入一个循环内,就可建立一个条件,它必须在继续程序之前满足。亦可添加一个`static`计数器或者另一些设备,允许循环在放弃以前尝试数种不同的方法。这样一来,我们的程序可以变得更加“健壮”。 输出如下: ``` Exception thrown in finally clause No exception in finally clause ``` 无论是否“抛”出一个异常,`finally`从句都会执行。 ## 9.6.1 用`finally`做什么 在没有“垃圾收集”以及“自动调用析构器”机制的一种语言中(注释⑤),`finally`显得特别重要,因为程序员可用它担保内存的正确释放——无论在`try`块内部发生了什么状况。但Java提供了垃圾收集机制,所以内存的释放几乎绝对不会成为问题。另外,它也没有构造器可供调用。既然如此,Java里何时才会用到`finally`呢? ⑤:“析构器”(Destructor)是“构造器”(Constructor)的反义词。它代表一个特殊的函数,一旦某个对象失去用处,通常就会调用它。我们肯定知道在哪里以及何时调用析构器。C++提供了自动的析构器调用机制,但Delphi的Object Pascal版本1及2却不具备这一能力(在这种语言中,析构器的含义与用法都发生了变化)。 除将内存设回原始状态以外,若要设置另一些东西,`finally`就是必需的。例如,我们有时需要打开一个文件或者建立一个网络连接,或者在屏幕上画一些东西,甚至设置外部世界的一个开关,等等。如下例所示: ``` //: OnOffSwitch.java // Why use finally? class Switch { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } public class OnOffSwitch { static Switch sw = new Switch(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... sw.off(); } catch(NullPointerException e) { System.out.println("NullPointerException"); sw.off(); } catch(IllegalArgumentException e) { System.out.println("IOException"); sw.off(); } } } ///:~ ``` 这里的目标是保证`main()`完成时开关处于关闭状态,所以将`sw.off()`置于`try`块以及每个异常控制器的末尾。但产生的一个异常有可能不是在这里捕获的,这便会错过`sw.off()`。然而,利用`finally`,我们可以将来自`try`块的关闭代码只置于一个地方: ``` //: WithFinally.java // Finally Guarantees cleanup class Switch2 { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } public class WithFinally { static Switch2 sw = new Switch2(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... } catch(NullPointerException e) { System.out.println("NullPointerException"); } catch(IllegalArgumentException e) { System.out.println("IOException"); } finally { sw.off(); } } } ///:~ ``` 在这儿,`sw.off()`已移至一个地方。无论发生什么事情,都肯定会运行它。 即使异常不在当前的`catch`从句集里捕获,`finally`都会在异常控制机制转到更高级别搜索一个控制器之前得以执行。如下所示: ``` //: AlwaysFinally.java // Finally is always executed class Ex extends Exception {} public class AlwaysFinally { public static void main(String[] args) { System.out.println( "Entering first try block"); try { System.out.println( "Entering second try block"); try { throw new Ex(); } finally { System.out.println( "finally in 2nd try block"); } } catch(Ex e) { System.out.println( "Caught Ex in first try block"); } finally { System.out.println( "finally in 1st try block"); } } } ///:~ ``` 该程序的输出展示了具体发生的事情: ``` Entering first try block Entering second try block finally in 2nd try block Caught Ex in first try block finally in 1st try block ``` 若调用了`break`和`continue`语句,`finally`语句也会得以执行。请注意,与作上标签的`break`和`continue`一道,`finally`排除了Java对`goto`跳转语句的需求。 ## 9.6.2 缺点:丢失的异常 一般情况下,Java的异常实现方案都显得十分出色。不幸的是,它依然存在一个缺点。尽管异常指出程序里存在一个危机,而且绝不应忽略,但一个异常仍有可能简单地“丢失”。在采用`finally`从句的一种特殊配置下,便有可能发生这种情况: ``` //: LostMessage.java // How an exception can be lost class VeryImportantException extends Exception { public String toString() { return "A very important exception!"; } } class HoHumException extends Exception { public String toString() { return "A trivial exception"; } } public class LostMessage { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } public static void main(String[] args) throws Exception { LostMessage lm = new LostMessage(); try { lm.f(); } finally { lm.dispose(); } } } ///:~ ``` 输出如下: ``` A trivial exception at LostMessage.dispose(LostMessage.java:21) at LostMessage.main(LostMessage.java:29) ``` 可以看到,这里不存在`VeryImportantException`(非常重要的异常)的迹象,它只是简单地被`finally`从句中的`HoHumException`代替了。 这是一项相当严重的缺陷,因为它意味着一个异常可能完全丢失。而且就象前例演示的那样,这种丢失显得非常“自然”,很难被人查出蛛丝马迹。而与此相反,C++里如果第二个异常在第一个异常得到控制前产生,就会被当作一个严重的编程错误处理。或许Java以后的版本会纠正这个问题(上述结果是用Java 1.1生成的)。