Java类库里面包含很多必须通过调用close方法手动关闭的资源。比如InputStream,OutputStream,和java.sql.Connection。关闭资源经常被客户端所忽视,情理之中也就导致了一些可怕的性能问题。尽管很多资源使用finalizers作为安全保障,但是finalizers却工作的不太好(Item 8)。以前,try – finally语句是保证资源被及时关闭的最好的方式,尽管遇到异常或者返回也是如此。
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new
FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
这可能看起来也不坏,但是你在加一个资源的时候它就变坏了。
// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
很多时候优秀的开发人员也会犯这种错,这可能很难相信。一开始,我把Java Puzzlers第88页弄错了,好几年都没人发现。事实上,在2007年Java类库中三分之二的close方法的使用都是错的。
甚至对于正确的使用try – finally去关闭资源的语句,比如上面的两个例子,都是存在隐含的缺陷的。在try代码块和finally代码块里面的代码是能够抛出异常的。比如,在firstLineOfFile方法里面,由于基础的物理设备的故障,调用readLine方法就会抛出一个异常。那么调用close就会因为相同原因也失败掉。在这种情况下,第二个异常就完全把第一个给抹去了。在异常栈追踪里面没有第一个异常,这样会在真实系统中极大地增加调试的复杂性,尤其在你想看第一个异常来诊断异常的时候。尽管可以写代码来阻止第二个异常抛出来以达到让第一个异常显示,但是事实上却没人这么做,因为代码太繁琐了。
当java 7引入了try -with-resources语句后,所有的这些问题一下子都被解决了。为了使用这种结构,资源必须实现AutoCloseable接口,这个接口仅仅由一个返回值为void的close方法组成。在java类库还有第三方类库中,很多类和接口都实现或者继承了AutoCloseable。如果你要写一个代表资源的类并且它必须被关闭,那么你的类也应该实现AutoCloseable。
这儿是我们使用try -with-resources的第一个例子:
// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
还有我们使用try -with-resources的第二个例子:
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
try -with-resources版本的代码不仅比之前的更短,可读性更高,并且它们能提供更好的诊断能力。考虑firstLineOfFile方法。如果readLine的调用还有(看不到的)close的调用都抛出了异常,那么为了支持前者后面的异常就被吃掉了。事实上,为了维护你真实想看的异常,多个异常可能都被吃掉了。这种被吃掉的异常也不是直接丢掉了;它们带着一个标记被打印在堆栈里面,这个标记会表明它们是被抑制了的。你也可以通过程序中的getSuppressed方法去访问它们,它是在Java 7里面加到Throwable里面的。你可以给try -with-resources语句加上catch语句,正如你通常在try - finally 语句里面添加一样。这样你就可以处理不被其他嵌套层代码所污染的异常了。
就像下面这个稍微有点不自然的例子,在这个版本中我们的firstLineOfFile方法不抛出异常,但是如果它打不开文件或者不能读,它接收一个默认值用来返回:
// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}