# 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生成的)。