首页 > 代码库 > Java7里try-with-resources分析

Java7里try-with-resources分析

这个所谓的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函数时出现异常,那么前面的异常就被称为Suppressed Exceptions,因此Throwable还有个addSuppressed函数可以把它们保存起来,当用户捕捉到close里抛出的异常时,就可以调用Throwable.getSuppressed函数来取出close之前的异常了。


总结:

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

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

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


Java7里try-with-resources分析