有时候我们可能会问:“当异常发生的时候,所有的东西都会被正确的清除吗?”,大多数情况下是相当安全的,不过在涉及到构造方法的时候问题就出现了。通常,构造器会把对象设置成安全的初始状态,但是它也可能会执行某些操作,比如,打开一个在使用完对象以及调用特定的清理方法之前不需要进行垃圾清理的文件。如果实在构造器了抛出异常,那么这么垃圾清理方法可能不会被正确执行,这就意味着在写你的构造方法的时候你必须十分用心。
也许你会把 finally 作为一种解决方案,但事实并不如此简单,应为finally在每次进行清理时都会执行一次。试想,如果一个构造器在它执行的过程中失败了,这样,这个对象的某些成员它并 没有成功创建,然而,在finally子句中这些部分也是要被清理的,这就是一个缺陷。
如下面这个例子:
package chapter12;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
// Other code that might throw exceptions
} catch (FileNotFoundException e) {
System.out.println("Could not open " + fname);
// Wasn’t open, so don’t close it
throw e;
} catch (Exception e) {
// All other exceptions must close it
try {
in.close();
} catch (IOException e2) { System.out.println("in.close()unsuccessful");
}
throw e; // Rethrow
} finally {
// Don’t close it here!!!
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch (IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch (IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
}
这个类很简单,也很容易理解。如果在InputFile的构造方法里抛出了
FileNotFoundException
,则说明文件没有被成功打开,因而,
in
这个
BufferedReader
的对象实例没有成功创建,此时如果在
finally
里面这样写
in.close()
,
这就是不合理的。所以在构造方法抛出
FileNotFoundException
时,我们并不需要调用
in.close()
对象,因为它没有关联到一个具体的文件上
;
不过当抛出其他
Excpetion
时,我们就需要关闭文件了,因为这时指定的文件已成功打开。另外需要说明的是
dispose
方法,这个方法是你在使用完
InputFile
对象之后需要调用的,它用来释放系统资源
(
例如文件句柄
)
。可能你会想到把这个功能放到
finalize
方法里面,然而我们知道我们无法确定
finalize
方法是否总是会被调用,即便会被调用我们也无法确定会在什么时候调用。从这里就牵扯出
java
语言的一个缺陷,在
java
里面只有内存的清理是自动执行的,除此之外其他的一切都不会自动清除,所以我们要通知客户端程序员
dispose
方法是有一定的职责的。
在构造方法可能抛出异常的情况下,比较安全的做法是使用嵌套的try,比如下面所演示的:
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while ((s = in.getLine()) != null)
; // Perform line-by-line processing here...
} catch (Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {
in.dispose();
}
} catch (Exception e) {
System.out.println("InputFile construction failed");
}
}
}
这个程序的逻辑不难分析。
InputFile
构造器内部的
try
块是可以生效的,当在构造方法里产生了异常,则会进入上面例子中外部的
try
块的
catch
子句,这是不用执行
dispose
方法;不过要是
InputFile
对象实例成功被构造,那么在内部的
try
块里就必须要执行
dispose
方法。按照上面程序段的写法就可以保证:只要
InputFile
的对象被成功创建,那么一定会执行
dispose
方法,释放系统资源。
对于构造方法可能会抛出异常的情况,通用的习惯用法是:在需要进行清理的对象被创建后,立即接上try-finally块,如下面的代码所示:
package chapter12;
class NeedsCleanup { // Construction can’t fail
private static long counter = 1;
private final long id = counter++;
public void dispose() {
System.out.println("NeedsCleanup " + id + " disposed");
}
}
class ConstructionException extends Exception {
}
class NeedsCleanup2 extends NeedsCleanup {
// Construction can fail:
public NeedsCleanup2() throws ConstructionException {
}
}
public class CleanupIdiom {
public static void main(String[] args) {
// Section 1:
NeedsCleanup nc1 = new NeedsCleanup();
try {
// ...
} finally {
nc1.dispose();
}
// Section 2:
// If construction cannot fail you can group objects:
NeedsCleanup nc2 = new NeedsCleanup();
NeedsCleanup nc3 = new NeedsCleanup();
try {
// ...
} finally {
nc3.dispose(); // Reverse order of construction
nc2.dispose();
}
// Section 3:
// If construction can fail you must guard each one:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
try {
// ...
} finally {
nc5.dispose();
}
} catch (ConstructionException e) { // nc5 constructor
System.out.println(e);
} finally {
nc4.dispose();
}
} catch (ConstructionException e) { // nc4 constructor
System.out.println(e);
}
}
}
按照这种写法,可以看出:对于那些构造方法不会产生异常的类,我们总能对它进行清理;对于构造方法有可能产生异常的类,我们总能在必要时对它进行清理。