客官,往这瞅
在我们日常的开发过程中,会调用需要手动close的资源。比如InputStream, OutputStream ,java.sql.Connection,socket等。别想着java有了GC,GC大大说,不是我家的,谁爱用谁管。得嘞,我们自己管。
没有对比就没有伤害,我们来伤害吧!
从以往来看,try-finally语句是保证资源正确关闭的最佳方式,即使是在程序抛出异常或返回的情况下:
static String firstLineOfFile(String path) throws IOException { BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { br.close(); } } return ""; } 复制代码
这样看起来并不坏,但是当添加第二个资源时,情况就会变得更糟:
static void copy(String src, String dst) throws IOException { InputStream in = new FileInputStream(src); try { OutputStream out = new FileOutputStream(dst); try { byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) >= 0) { out.write(buf, 0, n); } } finally { out.close(); } } finally { in.close(); } } 复制代码
即使是用 try-finally 语句关闭资源的正确代码,如前面两个代码示例所示,也有一个微妙的缺陷。 try-with- resources 块和 finally 块中的代码都可以抛出异常。 例如,在 firstLineOfFile 方法中,由于底层物理设备发生 故障,对 readLine 方法的调用可能会引发异常,并且由于相同的原因,调用 close 方法可能会失败。 在这种 情况下,第二个异常完全冲掉了第一个异常。 在异常堆栈跟踪中没有第一个异常的记录,这可能使实际系统中的调 试非常复杂——通常这是你想要诊断问题的第一个异常。 虽然可以编写代码来抑制第二个异常,但是实际上没有人这 样做,因为它太冗长了。
当java 7 引入了try-with-resources语句后,这些就都解决了。要使用这个语法糖,资源必须都实现AutoCloseAble接口.Java 类库和第三方库中的许多类和接口现在都实现了或继承了AutoCloseable接口。如果自己编写的类表示必须关闭的资源,那么也应该实现AutoCloseable接口。
public interface AutoCloseable { void close() throws Exception; } 复制代码
修改后的实例如下:
static String firstLineOfFile(String path) throws IOException { try (BufferedReader br = new BufferedReader( new FileReader(path))) { return br.readLine(); } } 复制代码
第二个实例:
static void copy(String src, String dst) throws IOException { try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n); } } 复制代码
不仅 try-with-resources 版本比原始版本更精简,更好的可读性,而且它们提供了更好的诊断。 考虑 firstLineOfFile 方法。 如果调用 readLine 和(不可见) close 方法都抛出异常,则后一个异常将被抑制 (suppressed),而不是前者。 事实上,为了保留你真正想看到的异常,可能会抑制多个异常。 这些抑制的异常没 有被抛弃, 而是打印在堆栈跟踪中,并标注为被抑制了。 你也可以使用 getSuppressed 方法以编程方式访问它 们,该方法在 Java 7 中已添加到的 Throwable 中。
是不是很神奇,来让我们看看原理吧
下面是第二个实例编译后的代码,看了是不是恍然大悟,原来一切都是编译器帮我们做好了
static void copy(String src, String dst) throws IOException { InputStream in = new FileInputStream(src); Throwable var3 = null; try { OutputStream out = new FileOutputStream(dst); Throwable var5 = null; try { byte[] buf = new byte[1024]; int n; while((n = in.read(buf)) >= 0) { out.write(buf, 0, n); } } catch (Throwable var29) { var5 = var29; throw var29; } finally { if (out != null) { if (var5 != null) { try { out.close(); } catch (Throwable var28) { var5.addSuppressed(var28); } } else { out.close(); } } } } catch (Throwable var31) { var3 = var31; throw var31; } finally { if (in != null) { if (var3 != null) { try { in.close(); } catch (Throwable var27) { var3.addSuppressed(var27); } } else { in.close(); } } } } 复制代码
相信细心的看官都发现了,实例的资源都是声明在括号中,并且实例化的吧。来我们继续说说怎么回事, java 9以前:
static String readData(String message) throws IOException { Reader inputString = new StringReader(message); BufferedReader br = new BufferedReader(inputString); try (BufferedReader br1 = br) { return br1.readLine(); } } 复制代码
如果不这样做,br资源不能释放,这个我们需要注意,但是在java 9以后,就不用额外声明变量了
static String readData(String message) throws IOException { Reader inputString = new StringReader(message); BufferedReader br = new BufferedReader(inputString); try (br) { return br.readLine(); } } 复制代码
结论很明确:在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 生成的代码更简洁, 更清晰,并且生成的异常更有用。 try-with-resources 语句在编写必须关闭资源的代码时会更容易,也不会出错,而 使用 try-finally 语句实际上是不可能的。
)