針對流的使用和關閉:
1)以前的寫法:在finally不斷try/finally進行資源的close操作
BufferedInputStream bin = null; BufferedOutputStream bout = null; try { bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt"))); int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } finally { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bout != null) { try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2)JDK8的寫法
public static void main(String[] args) { try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) { int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } }
背后的原因探究:try-with-resources 要求關閉的資源必須實現AutoCloseable接口
Test1:基礎測試
public class Connection implements AutoCloseable { public void doTask() throws Exception { System.out.println("Connection doTask"); } @Override public void close() throws Exception { System.out.println("Connection close()"); } }
調用的寫法
public class TryWithResource { public static void main(String[] args) { try (Connection conn = new Connection()) { conn.doTask(); } catch (Exception e) { e.printStackTrace(); } } }
執行結果:
Connection doTask
Connection close()
反編譯的結果:自動生成了之前手寫的try-catch-finally復雜邏輯
public class TryWithResource { public TryWithResource() { } public static void main(String[] args) { try { Connection conn = new Connection(); Throwable var2 = null; try { conn.doTask(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (conn != null) { if (var2 != null) { try { conn.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { conn.close(); } } } } catch (Exception var14) { var14.printStackTrace(); } } }
Test2:異常測試,Connection主動拋出異常
public class Connection implements AutoCloseable { public void doTask() throws Exception { throw new Exception("doTask()"); } @Override public void close() throws Exception { throw new Exception("close()"); } }
測試結果:可以拋出出問題地方
原因是反編譯時 var2.addSuppressed(var11) 作用(上面反編譯結果紅色字體部分)
注意:在使用try-with-resource的過程中,一定需要了解資源的close
方法內部的實現邏輯。否則還是可能會導致資源泄露。
public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } }
因為采用了裝飾器模式,在調用GZIPOutputStream::close可能發生異常,而無法繼續調用FileOutputStream::close方法
正確做法:應該在try-with-resource中單獨聲明最底層的資源,保證對應的close
方法一定能夠被調用。
public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); FileOutputStream fout = new FileOutputStream(new File("out.txt")); GZIPOutputStream out = new GZIPOutputStream(fout)) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } }