# 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生成的)。
- Java 编程思想
- 写在前面的话
- 引言
- 第1章 对象入门
- 1.1 抽象的进步
- 1.2 对象的接口
- 1.3 实现方案的隐藏
- 1.4 方案的重复使用
- 1.5 继承:重新使用接口
- 1.6 多态对象的互换使用
- 1.7 对象的创建和存在时间
- 1.8 异常控制:解决错误
- 1.9 多线程
- 1.10 永久性
- 1.11 Java和因特网
- 1.12 分析和设计
- 1.13 Java还是C++
- 第2章 一切都是对象
- 2.1 用引用操纵对象
- 2.2 所有对象都必须创建
- 2.3 绝对不要清除对象
- 2.4 新建数据类型:类
- 2.5 方法、参数和返回值
- 2.6 构建Java程序
- 2.7 我们的第一个Java程序
- 2.8 注释和嵌入文档
- 2.9 编码样式
- 2.10 总结
- 2.11 练习
- 第3章 控制程序流程
- 3.1 使用Java运算符
- 3.2 执行控制
- 3.3 总结
- 3.4 练习
- 第4章 初始化和清除
- 4.1 用构造器自动初始化
- 4.2 方法重载
- 4.3 清除:收尾和垃圾收集
- 4.4 成员初始化
- 4.5 数组初始化
- 4.6 总结
- 4.7 练习
- 第5章 隐藏实现过程
- 5.1 包:库单元
- 5.2 Java访问指示符
- 5.3 接口与实现
- 5.4 类访问
- 5.5 总结
- 5.6 练习
- 第6章 类复用
- 6.1 组合的语法
- 6.2 继承的语法
- 6.3 组合与继承的结合
- 6.4 到底选择组合还是继承
- 6.5 protected
- 6.6 累积开发
- 6.7 向上转换
- 6.8 final关键字
- 6.9 初始化和类装载
- 6.10 总结
- 6.11 练习
- 第7章 多态性
- 7.1 向上转换
- 7.2 深入理解
- 7.3 覆盖与重载
- 7.4 抽象类和方法
- 7.5 接口
- 7.6 内部类
- 7.7 构造器和多态性
- 7.8 通过继承进行设计
- 7.9 总结
- 7.10 练习
- 第8章 对象的容纳
- 8.1 数组
- 8.2 集合
- 8.3 枚举器(迭代器)
- 8.4 集合的类型
- 8.5 排序
- 8.6 通用集合库
- 8.7 新集合
- 8.8 总结
- 8.9 练习
- 第9章 异常差错控制
- 9.1 基本异常
- 9.2 异常的捕获
- 9.3 标准Java异常
- 9.4 创建自己的异常
- 9.5 异常的限制
- 9.6 用finally清除
- 9.7 构造器
- 9.8 异常匹配
- 9.9 总结
- 9.10 练习
- 第10章 Java IO系统
- 10.1 输入和输出
- 10.2 增添属性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File类
- 10.5 IO流的典型应用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 压缩
- 10.9 对象序列化
- 10.10 总结
- 10.11 练习
- 第11章 运行期类型识别
- 11.1 对RTTI的需要
- 11.2 RTTI语法
- 11.3 反射:运行期类信息
- 11.4 总结
- 11.5 练习
- 第12章 传递和返回对象
- 12.1 传递引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只读类
- 12.5 总结
- 12.6 练习
- 第13章 创建窗口和程序片
- 13.1 为何要用AWT?
- 13.2 基本程序片
- 13.3 制作按钮
- 13.4 捕获事件
- 13.5 文本字段
- 13.6 文本区域
- 13.7 标签
- 13.8 复选框
- 13.9 单选钮
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 视窗化应用
- 13.16 新型AWT
- 13.17 Java 1.1用户接口API
- 13.18 可视编程和Beans
- 13.19 Swing入门
- 13.20 总结
- 13.21 练习
- 第14章 多线程
- 14.1 反应灵敏的用户界面
- 14.2 共享有限的资源
- 14.3 堵塞
- 14.4 优先级
- 14.5 回顾runnable
- 14.6 总结
- 14.7 练习
- 第15章 网络编程
- 15.1 机器的标识
- 15.2 套接字
- 15.3 服务多个客户
- 15.4 数据报
- 15.5 一个Web应用
- 15.6 Java与CGI的沟通
- 15.7 用JDBC连接数据库
- 15.8 远程方法
- 15.9 总结
- 15.10 练习
- 第16章 设计模式
- 16.1 模式的概念
- 16.2 观察器模式
- 16.3 模拟垃圾回收站
- 16.4 改进设计
- 16.5 抽象的应用
- 16.6 多重分发
- 16.7 访问器模式
- 16.8 RTTI真的有害吗
- 16.9 总结
- 16.10 练习
- 第17章 项目
- 17.1 文字处理
- 17.2 方法查找工具
- 17.3 复杂性理论
- 17.4 总结
- 17.5 练习
- 附录A 使用非JAVA代码
- 附录B 对比C++和Java
- 附录C Java编程规则
- 附录D 性能
- 附录E 关于垃圾收集的一些话
- 附录F 推荐读物