诸如流、文件等系统资源,当其被打开时,需要手动关闭,要不必将引起一场大灾害。
传统的try cache finally
在java7之前,我们可以使用finally来确保资源被关闭,例如:
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null)
br.close();
}
}
但是如果br.readLine()有异常,br.close()也有异常抛出,由于一次只能抛一次异常,try的异常会被抑制,finally的异常会被抛出,导致异常丢失。以下是具体演示:
public class Connection implements AutoCloseable {
public void sendData() throws Exception {
throw new Exception("send data");
}
@Override
public void close() throws Exception {
throw new Exception("close");
}
}
public class TryWithResource {
public static void main(String[] args) {
try{
test();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void test()throws Exception{
Connection conn = null;
try{
conn = new Connection();
conn.sendData();
} finally{
if(conn !=null) {
conn.close();
}
}
}
}
运行结果如下:
java.lang.Exception: close
at cn.wangjy.study.trywithresources.Connection.close(Connection.java:18)
at cn.wangjy.study.trywithresources.TryWithResource.test(TryWithResource.java:84)
at cn.wangjy.study.trywithresources.TryWithResource.main(TryWithResource.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
用try-with-resources实现
JAVA 7之后,我们盼来福音,再也不需要在finally里面写一堆if(xxx !=null)xxx.close()的代码,只要资源实现了AutoCloseable或者Closeable接口,try-with-resources能帮其自动关闭。将以上的代码改写为try-with-resources
public class TryWithResource {
public static void main(String[] args) {
try(Connection conn =new Connection()) {
conn.sendData();
}catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
java.lang.Exception: send data
at cn.wangjy.study.trywithresources.Connection.sendData(Connection.java:13)
at cn.wangjy.study.trywithresources.TryWithResource.main(TryWithResource.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Suppressed: java.lang.Exception: close
at cn.wangjy.study.trywithresources.Connection.close(Connection.java:18)
at cn.wangjy.study.trywithresources.TryWithResource.main(TryWithResource.java:72)
... 5 more
可以看到,代码编写也十分简洁。虽然我们没有用finally的代码块写明close(),但是程序仍然会自动执行close()方法,并且异常信息也没有丢失。信息中多了一个Suppressed的提示,附带上close的异常。这个异常其实由两个异常组成, 执行close()的异常是被Suppressed的异常。
原理是什么呢?
反编译了TryWithResource的class文件,代码如下:
public static void main(String[] args) {
try {
Connection conn = new Connection();
Throwable localThrowable3 = null;
try {
conn.sendData();
} catch (Throwable localThrowable1) {
localThrowable3 = localThrowable1;
throw localThrowable1;
} finally {
if (conn != null) if (localThrowable3 != null){
try {
conn.close();
} catch (Throwable localThrowable2) {
localThrowable3.addSuppressed(localThrowable2);
}
}else{
conn.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
可见,编译器在编译的时候自动帮我们加了finally块,并添加了资源的close方法,所以例子的close会在运行的时候被执行。细心的你发现了吗,代码中还多了localThrowable3.addSuppressed(localThrowable2)的方法。java7之后,Throwable类新增了addSuppressed方法,支持将一个异常附加到另一个异常身上,从而避免异常屏蔽。这样输出的异常信息会附带Suppressed的提示,参考上图的运行结果。
try-with-resources 也支持声明多个资源
如下代码:
package org.fenixsoft.clazz;
import org.junit.Test;
import java.io.Closeable;
import java.io.IOException;
/**
* <b>description</b>: <br>
* <b>time</b>:2019/6/18 17:53 <br>
* <b>author</b>:微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
*/
public class Demo13 implements Closeable {
@Test
public void t0() throws IOException {
try (Demo13 demo13 = new Demo13();) {
System.out.println("路人甲Java");
}
}
@Test
public void t1() {
try {
try (Demo13 demo13 = new Demo13();
Demo13 demo131 = new Demo13();) {
System.out.println("路人甲Java");
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void close() throws IOException {
System.out.println("close");
}
}
反编译之后:
package org.fenixsoft.clazz;
import java.io.Closeable;
import java.io.IOException;
import org.junit.Test;
public class Demo13 implements Closeable {
public Demo13() {
}
@Test
public void t0() throws IOException {
Demo13 demo13 = new Demo13();
Throwable var2 = null;
try {
System.out.println("路人甲Java");
} catch (Throwable var11) {
var2 = var11;
throw var11;
} finally {
if(demo13 != null) {
if(var2 != null) {
try {
demo13.close();
} catch (Throwable var10) {
var2.addSuppressed(var10);
}
} else {
demo13.close();
}
}
}
}
@Test
public void t1() {
try {
Demo13 demo13 = new Demo13();
Throwable var2 = null;
try {
Demo13 demo131 = new Demo13();
Throwable var4 = null;
try {
System.out.println("路人甲Java");
} catch (Throwable var29) {
var4 = var29;
throw var29;
} finally {
if(demo131 != null) {
if(var4 != null) {
try {
demo131.close();
} catch (Throwable var28) {
var4.addSuppressed(var28);
}
} else {
demo131.close();
}
}
}
} catch (Throwable var31) {
var2 = var31;
throw var31;
} finally {
if(demo13 != null) {
if(var2 != null) {
try {
demo13.close();
} catch (Throwable var27) {
var2.addSuppressed(var27);
}
} else {
demo13.close();
}
}
}
} catch (IOException var33) {
var33.printStackTrace();
}
}
public void close() throws IOException {
System.out.println("close");
}
}
代码中有addSuppressed
方法,关于addSuppressed方法的介绍见:java异常处理新特性