Java IO 四大附加接口、try-with-resource
@author ixenos
四大附加接口 Closeable、Flushable、Readable、Appendable
Closeable:
void close() throws IOException 關閉此流並釋放與此流關聯的所有系統資源
java.io.closeable擴展了java.lang.AutoCloseable,因此,對任何Closeable進行操作時,都可以使用try-with-resources語句
- try-with-resources 可以聲明一個或多個資源,資源之間用分號隔開。
-
1 try ( 2 java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); 3 java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) 4 ) { 5 ... 6 }
有關 try-with-resource 特性的具體分析請見下文。
Flushable:
void flush() 將所有已緩沖輸出寫入底層流
Readable:
int read(CharBuffer cb) 形參中的CharBuffer類擁有按順序和隨機地進行讀寫訪問的方法,它表示一個內存中的緩沖區或者一個內存映像(虛擬內存)的文件
Appendable:
Appendable append(char c) 添加的單個字符
Appendable append(CharSequence s) 添加字符序列,CharSequence接口描述了一個char值序列的基本屬性,String、CharBuffer、StringBuffer、StringBuilder都實現了CharSequence,所以可以傳入這些類型的對象
以上兩個方法,都返回this
四大IO抽象類與附加接口的關系
1、InputStream、OutputStream、Reader、Writer都實現了Closeable和AutoCloseable接口,因此,都可以使用 try-with-resources 語句
2、OutputStream、Writer實現了Flushable接口
3、Reader實現了Readable接口
4、Writer實現了Appendable接口
try-with-resource
try-with-resource 是Java SE 7 加入AutoCloseable接口后才有的,closeable接口繼承了AutoCloseable接口。
AutoCloseable接口規定了自動關閉資源:
The close() method of an AutoCloseable object is called automatically when exiting a try-with-resources block for which the object has been declared in the resource specification header.
1、這個所謂的try-with-resources,是個語法糖。實際上就是自動調用資源的close()函數。
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 }
2、可以在一個 try
-with-resources 語句中聲明一個或多個資源(用分號隔開),但要注意資源的 close
方法調用順序與它們的創建順序相反
1 try ( 2 java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); 3 java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) 4 )
資源的close方法在JVM中的調用順序是: writer.close() zf.close()
3、下面從編繹器生成的字節碼來分析下,try-with-resources到底是怎樣工作的:
TryStudy實現了AutoCloseable接口,下面來看下test函數的字節碼:
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函數進行資源回收。
try-with-resource與異常捕獲
1、從 被丟棄 到 被抑制 的異常 Suppressed Exceptions
再仔細分析,可以發現有一個Throwable.addSuppressed的調用,那么這個調用是什么呢?
使用了try-catch語句之后(try-with-resource也算是try-catch),有可能會出現兩種異常,一個是try塊里的異常,一個是調用close函數里拋出的異常。
a) 在JDK1.7 以前,一旦finally塊拋出了close函數里的異常,前面try塊catch的異常被丟棄了!
b) 而在 try-with-resources 語句中,如果在調用close函數時出現異常(注意這個前提),那么前面的異常就被稱為Suppressed Exceptions,因此Throwable還有個addSuppressed函數可以把它們保存起來,當用戶捕捉到close里拋出的異常時,就可以調用Throwable.getSuppressed函數來取出close之前的異常了。
2、注意:
一個 try
-with-resources 語句可以像普通的 try
語句那樣有 catch
和 finally
塊。
但是在try
-with-resources 語句中, 任意的 catch
或者 finally
塊都是在聲明的資源被關閉以后才運行。
3、catch多種異常,但拋出一種異常時
在JDK1.7之前catch多個異常是這樣的:
1 try{ 2 //邏輯代碼 3 }catch (IOException ex) { 4 logger.log(ex); 5 throw new SpecialException(); 6 catch (SQLException ex) { 7 logger.log(ex); 8 throw new SpecialException(); 9 }
從上述代碼中可以看出這樣寫非常的難看,並且會出現許多重復的代碼。
JDK1.7及以后可以這樣:
1 try{ 2 //邏輯代碼 3 }catch (IOException | SQLException ex) { 4 logger.log(ex); 5 throw new SpecialException(); 6 }
注:上面例子中的ex是隱式的final不可以在catch塊中改變ex。
4、在JDK1.7以前的版本,在方法聲明中聲明拋出的異常如果在方法體內沒有拋出時不被允許的,如下:
1 static class FirstException extends Exception { 2 3 } 4 5 static class SecondException extends Exception { 6 7 } 8 9 public void rethrowException(String exceptionName) throws Exception { 10 11 try { 12 13 if (exceptionName.equals("First")) { 14 //如果異常名稱為"First",則拋出異常一 15 throw new FirstException(); 16 17 } else { 18 //否則的話,則拋出異常二 19 throw new SecondException(); 20 21 } 22 23 } catch (Exception e) { 24 25 throw e; 26 } 27 }
JDK1.7及以后版本:
1 static class FirstException extends Exception { 2 3 } 4 5 static class SecondException extends Exception { 6 7 } 8 9 public void rethrowException(String exceptionName) throws Exception, FirstException, SecondException { 10 try { 11 // 邏輯代碼 12 }catch (Exception e) { 13 14 throw e; 15 } 16 }