此系列文章为本人对《Effective Java》一书的学习笔记,主要是记录对书中重点内容的理解。
既然有缘看到此文,那么希望能对你有所帮助。
本文对应原书第9条 try-with-resources优先于try-finally
引子
相信每一位Javaer都曾经在网上拷贝过代码,很多时候你拷的内容看起来相当的凌乱,你忧心忡忡,生怕没法使用,但当你小心翼翼放入项目之中,却惊喜的发现it works。
激动之余你自己研究起来,你发现代码里使用到了很多的Stream
,各种try
、flush
、finally
、close
。
在Java 7之前,你可能感叹:“厉害啊,操作666”;
在Java 7以后,你应该叹息:“又是一段被时代抛弃的代码”。
缘由
Java类库中包括了很多调用close方法来手工关闭的资源,例如:
- InputStream
- OutputStream
- java.sql.connection
- …
相必不用我提大家也知道,若这些资源没有进行正常的关闭,会造成怎样的后果。
尽管这其中有很多的资源都是用终结方法作为安全网(安全网可参考《Effective Java – 避免使用finalize方法》),但是终结方法本身的局限性使得效果并不如人意。
所以,在早期我们需要使用try-finally
来关闭资源。如:
public static String readFirstLine() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("path"));
try {
return br.readLine(); // 语句1
} finally {
br.close(); // 语句2
}
}
这样看起来还凑合,但这毕竟只用了一个资源,如果资源数目增加,那么代码就会越发臃肿。
另外,还有一个重要的原因,就是上述代码的语句1和语句2,都有可能发生异常。
举个栗子:底层的物理设备发生了异常,那么readLine
方法会抛出异常,然后close
方法也会发生异常,这下有趣的来了,第二个异常会把第一个异常“吃掉”(抹掉),在异常堆栈信息中,你找不到有关第一个异常的记录,这样调试会变得异常复杂。
救星
Java 7引入了try-with-resources
语句,以上的问题迎刃而解。
所谓的try-with-resources
,就是在try关键字后面加上一个括号,但要使用它,必须实现AutoClosable
接口。
我们改造一下第一段代码:
public static String readFirstLine() throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("path"))) {
return br.readLine();
}
}
不仅仅变得简洁,就连上述的异常被抹掉的情况也一并处理好了。这里try-with-resources
依然包含两个语句,一个是readLine
方法,还有一个是隐藏的close
方法。
如果这两个语句都出现了异常,那么,后一个异常会被抑制(如果是多个异常,除了第一个,后续的都会被抑制)。
这样做是为了保留你想看到的那个异常,而且这些被抑制的异常也并非被简单的丢弃了,它们依然会被打印在堆栈轨迹中,并且注明它们是被抑制的,另外通过getSuppressed
方法可以访问的到(可以说是相当周到了)。
P.S. 中文版书中,对于英文原版的 “
suppressed
” 翻译为“禁止
”,个人认为不妥,所以采用了更为广泛运用的“抑制
”作为翻译。
等等,这还没完,try-with-resources
还可以使用catch
子句,这样可以直接轻松的处理掉异常。我们继续改造,不对异常进行抛出,而是返回一个默认值:
public static String readFirstLine() {
try(BufferedReader br = new BufferedReader(new FileReader("path"))) {
return br.readLine();
} catch (IOException e) {
// 省略异常打印
return DEFAULT_VALUE;
}
}
总结
在使用必须关闭的资源时,我们要优先使用 try-with-resources
,而不是 try-finally
。前者的代码编写简单且更加简洁有效,所以赶紧动手把你项目中的try-finally
优化一下吧。
水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义