try-with-resources分析

转自:https://blog.csdn.net/hengyunabc/article/details/18459463

这个所谓的try-with-resources,是个语法糖。实际上就是自动调用资源的close()函数。和Python里的with语句差不多。

例如:

[java]  view plain  copy
  1. static String readFirstLineFromFile(String path) throws IOException {  
  2.     try (BufferedReader br = new BufferedReader(new FileReader(path))) {  
  3.         return br.readLine();  
  4.     }  
  5. }  

可以看到try语句多了个括号,而在括号里初始化了一个BufferedReader。

这种在try后面加个括号,再初始化对象的语法就叫try-with-resources。

实际上,相当于下面的代码(其实略有不同,下面会说明):

[java]  view plain  copy
  1. static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {  
  2.     BufferedReader br = new BufferedReader(new FileReader(path));  
  3.     try {  
  4.         return br.readLine();  
  5.     } finally {  
  6.         if (br != null) br.close();  
  7.     }  
  8. }  

很容易可以猜想到,这是编绎器自动在try-with-resources后面增加了判断对象是否为null,如果不为null,则调用close()函数的的字节码。


只有实现了java.lang.AutoCloseable接口,或者java.io.Closable(实际上继随自java.lang.AutoCloseable)接口的对象,才会自动调用其close()函数。

有点不同的是java.io.Closable要求一实现者保证close函数可以被重复调用。而AutoCloseable的close()函数则不要求是幂等的。具体可以参考Javadoc。


下面从编绎器生成的字节码来分析下,try-with-resources到底是怎样工作的:

[java]  view plain  copy
  1. public class TryStudy implements AutoCloseable{  
  2.     static void test() throws Exception {  
  3.         try(TryStudy tryStudy = new TryStudy()){  
  4.             System.out.println(tryStudy);  
  5.         }  
  6.     }  
  7.     @Override  
  8.     public void close() throws Exception {  
  9.     }  
  10. }  
TryStudy实现了AutoCloseable接口,下面来看下test函数的字节码:

[java]  view plain  copy
  1. static test()V throws java/lang/Exception   
  2.   TRYCATCHBLOCK L0 L1 L2   
  3.   TRYCATCHBLOCK L3 L4 L4   
  4.  L5  
  5.   LINENUMBER 21 L5  
  6.   ACONST_NULL  
  7.   ASTORE 0  
  8.   ACONST_NULL  
  9.   ASTORE 1  
  10.  L3  
  11.   NEW TryStudy  
  12.   DUP  
  13.   INVOKESPECIAL TryStudy.<init> ()V  
  14.   ASTORE 2  
  15.  L0  
  16.   LINENUMBER 22 L0  
  17.   GETSTATIC java/lang/System.out : Ljava/io/PrintStream;  
  18.   ALOAD 2  
  19.   INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V  
  20.  L1  
  21.   LINENUMBER 23 L1  
  22.   ALOAD 2  
  23.   IFNULL L6  
  24.   ALOAD 2  
  25.   INVOKEVIRTUAL TryStudy.close ()V  
  26.   GOTO L6  
  27.  L2  
  28.  FRAME FULL [java/lang/Throwable java/lang/Throwable TryStudy] [java/lang/Throwable]  
  29.   ASTORE 0  
  30.   ALOAD 2  
  31.   IFNULL L7  
  32.   ALOAD 2  
  33.   INVOKEVIRTUAL TryStudy.close ()V  
  34.  L7  
  35.  FRAME CHOP 1  
  36.   ALOAD 0  
  37.   ATHROW  
  38.  L4  
  39.  FRAME SAME1 java/lang/Throwable  
  40.   ASTORE 1  
  41.   ALOAD 0  
  42.   IFNONNULL L8  
  43.   ALOAD 1  
  44.   ASTORE 0  
  45.   GOTO L9  
  46.  L8  
  47.  FRAME SAME  
  48.   ALOAD 0  
  49.   ALOAD 1  
  50.   IF_ACMPEQ L9  
  51.   ALOAD 0  
  52.   ALOAD 1  
  53.   INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V  
  54.  L9  
  55.  FRAME SAME  
  56.   ALOAD 0  
  57.   ATHROW  
  58.  L6  
  59.   LINENUMBER 24 L6  
  60.  FRAME CHOP 2  
  61.   RETURN  
  62.   LOCALVARIABLE tryStudy LTryStudy; L0 L7 2  
  63.   MAXSTACK = 2  
  64.   MAXLOCALS = 3  

从字节码里可以看出,的确是有判断tryStudy对象是否为null,如果不是null,则调用close函数进行资源回收。

再仔细分析,可以发现有一个Throwable.addSuppressed的调用,那么这个调用是什么呢?

其实,上面的字节码大概是这个样子的(当然,不完全是这样的,因为汇编的各种灵活的跳转用Java是表达不出来的):

[java]  view plain  copy
  1. static void test() throws Exception {  
  2.     TryStudy tryStudy = null;  
  3.     try{  
  4.         tryStudy = new TryStudy();  
  5.         System.out.println(tryStudy);  
  6.     }catch(Throwable suppressedException) {  
  7.         if (tryStudy != null) {  
  8.             try {  
  9.                 tryStudy.close();  
  10.             }catch(Throwable e) {  
  11.                 e.addSuppressed(suppressedException);  
  12.                 throw e;  
  13.             }  
  14.         }  
  15.         throw suppressedException;  
  16.     }  
  17. }  

有点晕是吧,其实很简单。使用了try-with-resources语句之后,有可能会出现两个异常,一个是try块里的异常,一个是调用close函数里抛出的异常。

当然,平时我们写代码时,没有关注到。一般都是再抛出close函数里的异常,前面的异常被丢弃了。

如果在调用close函数时出现异常,那么close的异常就被称为Suppressed Exceptions,因此Throwable还有个addSuppressed函数可以把它们保存起来,当用户捕捉到close里抛出的异常时,就可以调用Throwable.getSuppressed函数来取出close的异常了。try异常正常被捕获,后面的异常交给suppressed,代码如下:

  1. package cn.bjut.study;  
  2.   
  3. class MyAutoClosable implements AutoCloseable {  
  4.   
  5.     @Override  
  6.     public String toString() {  
  7.         return "MyAutoClosable::toString";  
  8.     }  
  9.   
  10.     @Override  
  11.     public void close() {  
  12.         System.out.println("MyAutoClosable::close");  
  13.         throw new ArrayIndexOutOfBoundsException();  
  14.     }  
  15. }  
  16.   
  17. public class Main {  
  18.   
  19.     public static void main(String[] args) {  
  20.         try (MyAutoClosable closable = new MyAutoClosable()) {  
  21.             throw new ClassNotFoundException();  
  22.         } catch (Exception e) {  
  23.             System.out.println(e.getClass().getName());  
  24.             Throwable[] suppressed = e.getSuppressed();  
  25.             System.out.println(suppressed[0].getClass().getName());  
  26.         }  
  27.     }  
  28. }  

输出:
MyAutoClosable::close
java.lang.ClassNotFoundException
java.lang.ArrayIndexOutOfBoundsException


总结:

使用try-with-resources的语法可以实现资源的自动回收处理,大大提高了代码的便利性,和mutil catch一样,是个好东东。

用编绎器生成的字节码的角度来看,try-with-resources语法更加高效点。

java.io.Closable接口要求一实现者保证close函数可以被重复调用,而AutoCloseable的close()函数则不要求是幂等的。

参考:

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html

http://docs.oracle.com/javase/7/docs/api/java/io/Closeable.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值