閱讀527 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java7裏try-with-resources分析

這個所謂的try-with-resources,是個語法糖。實際上就是自動調用資源的close()函數。和Python裏的with語句差不多。

例如:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

可以看到try語句多了個括號,而在括號裏初始化了一個BufferedReader。

這種在try後麵加個括號,再初始化對象的語法就叫try-with-resources。

實際上,相當於下麵的代碼(其實略有不同,下麵會說明):

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

很容易可以猜想到,這是編繹器自動在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到底是怎樣工作的:

public class TryStudy implements AutoCloseable{
	static void test() throws Exception {
		try(TryStudy tryStudy = new TryStudy()){
			System.out.println(tryStudy);
		}
	}
	@Override
	public void close() throws Exception {
	}
}
TryStudy實現了AutoCloseable接口,下麵來看下test函數的字節碼:

  static test()V throws java/lang/Exception 
    TRYCATCHBLOCK L0 L1 L2 
    TRYCATCHBLOCK L3 L4 L4 
   L5
    LINENUMBER 21 L5
    ACONST_NULL
    ASTORE 0
    ACONST_NULL
    ASTORE 1
   L3
    NEW TryStudy
    DUP
    INVOKESPECIAL TryStudy.<init> ()V
    ASTORE 2
   L0
    LINENUMBER 22 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L1
    LINENUMBER 23 L1
    ALOAD 2
    IFNULL L6
    ALOAD 2
    INVOKEVIRTUAL TryStudy.close ()V
    GOTO L6
   L2
   FRAME FULL [java/lang/Throwable java/lang/Throwable TryStudy] [java/lang/Throwable]
    ASTORE 0
    ALOAD 2
    IFNULL L7
    ALOAD 2
    INVOKEVIRTUAL TryStudy.close ()V
   L7
   FRAME CHOP 1
    ALOAD 0
    ATHROW
   L4
   FRAME SAME1 java/lang/Throwable
    ASTORE 1
    ALOAD 0
    IFNONNULL L8
    ALOAD 1
    ASTORE 0
    GOTO L9
   L8
   FRAME SAME
    ALOAD 0
    ALOAD 1
    IF_ACMPEQ L9
    ALOAD 0
    ALOAD 1
    INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V
   L9
   FRAME SAME
    ALOAD 0
    ATHROW
   L6
    LINENUMBER 24 L6
   FRAME CHOP 2
    RETURN
    LOCALVARIABLE tryStudy LTryStudy; L0 L7 2
    MAXSTACK = 2
    MAXLOCALS = 3

從字節碼裏可以看出,的確是有判斷tryStudy對象是否為null,如果不是null,則調用close函數進行資源回收。

再仔細分析,可以發現有一個Throwable.addSuppressed的調用,那麼這個調用是什麼呢?

其實,上麵的字節碼大概是這個樣子的(當然,不完全是這樣的,因為匯編的各種靈活的跳轉用Java是表達不出來的):

	static void test() throws Exception {
		TryStudy tryStudy = null;
		try{
			tryStudy = new TryStudy();
			System.out.println(tryStudy);
		}catch(Throwable suppressedException) {
			if (tryStudy != null) {
				try {
					tryStudy.close();
				}catch(Throwable e) {
					e.addSuppressed(suppressedException);
					throw e;
				}
			}
			throw suppressedException;
		}
	}

有點暈是吧,其實很簡單。使用了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()函數則不要求是冪等的。

參考:

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

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

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


最後更新:2017-04-03 12:54:38

  上一篇:go C#之&quot;0x{0:x}&quot;
  下一篇:go HDU2178猜數字