针对流的使用和关闭:
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(); } }