9.6 用finally清除
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 生成的)。