[译]Better Resource Management with Java SE 7: Beyond Syntactic Sugar
[译]Better Resource Management with Java SE 7: Beyond Syntactic Sugar
原作者:Julien Ponge
原文地址:http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html
这篇文章表述了java 7解决自动资源管理问题的思路,翻译的时候尽量简化,因为作者废话太多。 ## 简介
一个典型的java程序会处理包括文件、流、socket、数据库连接等多种类型的资源。这些资源都需要占用实际的系统资源,所以我们在处理时候需要格外小心,需要确保即使是发生异常的时候,这些资源都能够被正确释放。经常会有数据库连接或文件流由于异常而没有正确关闭,导致系统资源用完的情况。
现在比较常用的资源管理方法大家都比较清楚了,初始化一个资源后,就要调用这个资源的close方法,一般会使用try/catch/finally块来确保最后一定能调用到close方法。但是即使这样,还是有时候会出现资源未正确关闭的情况,引起一些问题。
这篇文章表述了java 7解决自动资源管理问题的思路,这个思路最早是由OpenJdk的Project Coin提出的,叫做try-with-resources表达式. 这种方法类似foreach的语法糖的方案,但是又不仅仅是增加一个语法糖,而且还解决了异常覆盖的问题。
try-with-resources表达式
以前的try/catch/finally块会导致在业务在代码里有很多“boilerplate code”, 类似这样:
private void correctWriting() throws IOException {
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream("data"));
out.writeInt(666);
out.writeUTF("Hello");
} finally {
if (out != null) {
out.close();
}
}
}
使用try-with-resources后,会写成下面这个样子:
private void writingWithARM() throws IOException {
try (DataOutputStream out
= new DataOutputStream(new FileOutputStream("data"))) {
out.writeInt(666);
out.writeUTF("Hello");
}
}
try-with-resources有几点要注意的:
- 在try block结束之后,无论是正常结束还是异常退出,都会执行资源的close方法以关闭资源;
- 如果是在try里面定义多个资源,每个定义语句之间用分号分隔。
- 在这样的try后面加catch和finally也是和以前一样的。
- 在try-with-resources statement结束时会依据资源的声明顺序反向调用close方法进行关闭。
Auto-Closeable类
只有实现了java.lang.AutoCloseable接口的类才能在try-with-resources中自动释放,这个接口有一个close方法需要资源类去实现。java库中的好多资源类都实现了这个接口,包括java.io、java.nio、javax.crypto、java.security、java.util.zip、java.util.jar、javax.net和java.sql packages。
下面再看一个例子,
public class AutoClose implements AutoCloseable {
@Override
public void close() {
System.out.println(">>> close()");
throw new RuntimeException("Exception in close()");
}
public void work() throws MyException {
System.out.println(">>> work()");
throw new MyException("Exception in work()");
}
public static void main(String[] args) {
try (AutoClose autoClose = new AutoClose()) {
autoClose.work();
} catch (MyException e) {
e.printStackTrace();
}
}
}
class MyException extends Exception {
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
这个例子中,work方法和close方法都会抛出异常,最后在main方法中打印出的异常堆栈是:
>>> work()
>>> close()
MyException: Exception in work()
at AutoClose.work(AutoClose.java:11)
at AutoClose.main(AutoClose.java:16)
Suppressed: java.lang.RuntimeException: Exception in close()
at AutoClose.close(AutoClose.java:6)
at AutoClose.main(AutoClose.java:17)
从这里可以看出,确实是先调用了close方法,然后再进入了catch语句,但是这里可以看到close语句中抛出的异常是被Suppressed, 关于这点,会在接下来的部分讲到。
Exception Masking
我们来看下,如果用try-catch-finally会是什么样的情况:
public static void runWithMasking() throws MyException {
AutoClose autoClose = new AutoClose();
try {
autoClose.work();
} finally {
autoClose.close();
}
}
public static void main(String[] args) {
try {
runWithMasking();
} catch (Throwable t) {
t.printStackTrace();
}
}
输出结果如下:
>>> work()
>>> close()
java.lang.RuntimeException: Exception in close()
at AutoClose.close(AutoClose.java:6)
at AutoClose.runWithMasking(AutoClose.java:19)
at AutoClose.main(AutoClose.java:52)
这里展示的来的是信息是close()抛出的异常,而我们知道,work()函数也抛出来异常,实际上我们也更关心work()抛出的异常,而work()中抛出的异常信息却没有展示出来。出现这样的原因是只能有一个异常被抛出来,我们虽然也可以通过getCause获取nested Exception来得到work()中抛出的异常信息,但总的来说,还是是有点不优雅的。这叫做Exception Masking。
Supporting “Suppressed” Exceptions
通过上一段我们可以看到Exception Masking对于我们的实际使用,是存在一些不方便的地方。所以Java 7中引入了一个叫做“suppressed” exceptions, 这个“suppressed” exceptions能够挂到主异常上。 Java 7在java.lang.Throwable中增加了两个方法:
//appends a suppressed exception to another one, so as to avoid exception masking.
public final void addSuppressed(Throwable exception)
//gets the suppressed exceptions that were added to an exception.
public final Throwable[] getSuppressed()
这两个方法就是专门为try-with-resources而引入,解决Exception Masking问题而设计的。如果不通过try-with-resources,自己也是可以自己写代码来实现这样的“suppressed” exceptions,只是会比较麻烦,大概是下面这样:
public static void runWithoutMasking() throws MyException {
AutoClose autoClose = new AutoClose();
MyException myException = null;
try {
autoClose.work();
} catch (MyException e) {
myException = e;
throw e;
} finally {
if (myException != null) {
try {
autoClose.close();
} catch (Throwable t) {
myException.addSuppressed(t);
}
} else {
autoClose.close();
}
}
}
当work()方法抛出异常后,在catch中使用一个变量保存下当前异常,在finally中判断如果这个变量不为空,就为close()方法增加try-catch块,当close()抛出异常进入catch块后,将close()抛出的异常挂到主异常上,就完成了。
Syntantic Sugar Demystified
使用try-with-resources方法也能实现相同的效果,如下面的代码:
public static void runInARM() throws MyException {
try (AutoClose autoClose = new AutoClose()) {
autoClose.work();
}
}
代码经过编译后,再通过反编译工具JD-GUI转成java代码,来看一下:
public static void runInARM() throws MyException {
AutoClose localAutoClose = new AutoClose();
Object localObject1 = null;
try {
localAutoClose.work();
} catch (Throwable localThrowable2) {
localObject1 = localThrowable2;
throw localThrowable2;
} finally {
if (localAutoClose != null) {
if (localObject1 != null) {
try {
localAutoClose.close();
} catch (Throwable localThrowable3) {
localObject1.addSuppressed(localThrowable3);
}
} else {
localAutoClose.close();
}
}
}
}
基本上和我们刚在自己手写的代码是一样的了。try-with-resources表达式,自己就实现了“suppressed” exceptions的功能,简化了我们很多事情。而且如果try-with-resources中定义了多个资源,这个简化的意义就更大。让我们看一个在try块中定义多个资源的例子:
private static void compress(String input, String output) throws IOException {
try(
FileInputStream fin = new FileInputStream(input);
FileOutputStream fout = new FileOutputStream(output);
GZIPOutputStream out = new GZIPOutputStream(fout)
) {
byte[] buffer = new byte[4096];
int nread = 0;
while ((nread = fin.read(buffer)) != -1) {
out.write(buffer, 0, nread);
}
}
}
在这个例子中,try块定义了三个资源,通过反编译生成的代码就会非常复杂,那么有没有办法简化一下, 是有的,比如这样:
private static void compress(String input, String output) throws IOException {
try(
FileInputStream fin = new FileInputStream(input);
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(output))
) {
byte[] buffer = new byte[4096];
int nread = 0;
while ((nread = fin.read(buffer)) != -1) {
out.write(buffer, 0, nread);
}
}
}
这样做确实简化了,但也引入了一个问题,比如在执行out.close()时,可能会发生异常,导致GZIPOutputStream包装的FileOutputStream的close()方法没有正确执行,引起泄露。而上一段代码就没这个问题,因为FileOutputStream fout是单独定义出来的,会单独调用fout.close()。
总结
try-with-resources不仅实现了资源的自动关闭,也通过实现”Suppressed” Exceptions解决了Exception Masking的问题。