Try-with-resources是JDK1.7中一个新的异常处理机制,它能够很容易地关闭在try-catch语句块中使用的资源。
利用Try-Catch-Finally管理资源的释放
在java7以前,程序中使用的资源需要被明确地关闭,我们需要在finally块中编写一大堆繁琐的代码,比如下面这种:
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream());
//TODO
}catch (Exception e){
e.printStackTrace();
}finally {
if (reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null){
writer.close();
}
}
try语句块中有可能抛出异常,finally语句块中有一个地方会能出异常。
不论try语句块中是否有异常抛出,finally语句块始终会被执行。这意味着,不论try语句块中发生什么,reader
都会被关闭,或者说都会试图被关闭。如果关闭失败,reader.close()
方法也可能会抛出异常。
假设try语句块抛出一个异常,然后finally语句块被执行。同样假设finally语句块也抛出了一个异常。那么哪个异常会根据调用栈往外传播?
即使try语句块中抛出的异常与异常传播更相关,最终还是finally语句块中抛出的异常会根据调用栈向外传播。
在JDK1.7中,对于上面的例子可以用try-with-resource 结构这样写:
try(BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)
){
String body;
while ((body = reader.readLine()) != null){
String res = body.equalsIgnoreCase("query time") ? new Date().toString() : "Bad order!";
writer.println(res);
}
}catch (IOException e){
e.printStackTrace();
}
上面的例子在try关键字后的括号里创建了两个资源——reader
和writer
。当程序运行离开try语句块时,这两个资源都会被自动关闭。
这些资源将按照他们被创建顺序的逆序来关闭。首先writer
会被关闭,然后reader
会被关闭。
资源被自动释放的关键:实现Closeable接口
这个try-with-resources结构里不仅能够操作java内置的类。你也可以在自己的类中实现java.lang.AutoCloseable接口,然后在try-with-resources结构里使用这个类。
AutoClosable 接口仅仅有一个方法,接口定义如下:
public interface AutoClosable {
public void close() throws Exception;
}
任何实现了这个接口的方法都可以在try-with-resources结构中使用。
其实try-with-resources就是一个语法糖,编译器自动为我们做了一下工作:
当try语句块在执行中遇到异常时,catch语句会捕获到抛出的异常,但并不会马上处理这些异常,编译器会调用实现了AutoCloseable接口的待释放对象的close方法,执行资源的释放工作
当对象的close方法执行时遇到到异常,此时编译器不会直接抛出,而是把产生的异常都记录下来。这么做的好处是不会丢失任何异常。在java7之前,这种做法需要实现自己的异常类,而在java7中,已经对Throwable类进行了修改以支持这种情况。在java7中为Throwable类增加addSuppressed方法。当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过addSuppressed方法把这些被抑制的方法记录下来。被抑制的异常会出现在抛出的异常的堆栈信息中,也可以通过getSuppressed方法来获取这些异常。这样做的好处是不会丢失任何异常,方便开发人员进行调试。
编译器为我们做的工作大体如下所示
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream());
//TODO
}catch (Exception e){
if (reader != null){
try {
reader.close();
} catch (IOException e1) {
e.addSuppressed(e1);
}
}
if (writer != null){
writer.close();
}
e.printStackTrace();
}
}
我编写了一个自定义的对象来模拟了一下资源关闭的流程,简单明了
package chp2.bio;
import java.io.Closeable;
import java.io.IOException;
/**
* Created by ThinkPad on 2017/12/9.
*/
public class CloseDemo implements AutoCloseable {
public static void main(String[] args) {
try(CloseDemo demo = new CloseDemo()) {
System.out.println("代码执行出现ClassNotFound异常");
throw new ClassNotFoundException("ClassDemo NotFound exception");
}catch (Exception e){
System.out.println("捕获到异常:" + e.getLocalizedMessage());
System.out.println("异常类型:" + e.getClass().getName());
Throwable[] throwables = e.getSuppressed();
for (Throwable throwable : throwables) {
System.out.println("被抑制的异常:" + throwable.getMessage());
System.out.println("被抑制的异常名称:" + throwable.getClass().getName());
}
}
}
@Override
public void close() {
System.out.println("CloseDemo 开始关闭");
throw new ArrayIndexOutOfBoundsException("关闭demo出现异常");
}
}
输出结果:
代码执行出现ClassNotFound异常
CloseDemo 开始关闭
捕获到异常:ClassDemo NotFound exception
异常类型:java.lang.ClassNotFoundException
被抑制的异常:关闭demo出现异常
被抑制的异常名称:java.lang.ArrayIndexOutOfBoundsException