企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## [Try-With-Resources 用法](https://lingcoder.gitee.io/onjava8/#/book/15-Exceptions?id=try-with-resources-%e7%94%a8%e6%b3%95) 上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。 `InputFile.java`是一个特别棘手的情况,因为文件被打开(伴随所有可能因此产生的异常),然后它在对象的生命周期中保持打开状态。每次调用`getLine()`都可能导致异常,而且`dispose()`也是这种情况。这个例子只是好在它显示了事情可以混乱到什么地步。它还表明了你应该尽量不要那样设计代码(当然,你经常会遇到这种无法选择的代码设计的情况,因此你仍然必须要理解它)。 InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子: ~~~ // exceptions/InputFile2.java import java.io.*; import java.nio.file.*; import java.util.stream.*; public class InputFile2 { private String fname; public InputFile2(String fname) { this.fname = fname; } public Stream<String> getLines() throws IOException { return Files.lines(Paths.get(fname)); } public static void main(String[] args) throws IOException { new InputFile2("InputFile2.java").getLines() .skip(15) .limit(1) .forEach(System.out::println); } } ~~~ 输出为: ~~~ main(String[] args) throws IOException { ~~~ 现在,getLines() 全权负责打开文件并创建 Stream。 你不能总是轻易地回避这个问题。有时会有以下问题: 1. 需要资源清理 2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。 一个常见的例子是`java.io.FileInputStream`(将会在[附录:I/O 流](https://lingcoder.gitee.io/onjava8/#/./Appendix-IO-Streams)中提到)。要正确使用它,你必须编写一些棘手的样板代码: ~~~ // exceptions/MessyExceptions.java import java.io.*; public class MessyExceptions { public static void main(String[] args) { InputStream in = null; try { in = new FileInputStream( new File("MessyExceptions.java")); int contents = in.read(); // Process contents } catch(IOException e) { // Handle the error } finally { if(in != null) { try { in.close(); } catch(IOException e) { // Handle the close() error } } } } } ~~~ 当 finally 子句有自己的 try 块时,感觉事情变得过于复杂。 幸运的是,Java 7 引入了 try-with-resources 语法,它可以非常清楚地简化上面的代码: ~~~ // exceptions/TryWithResources.java import java.io.*; public class TryWithResources { public static void main(String[] args) { try( InputStream in = new FileInputStream( new File("TryWithResources.java")) ) { int contents = in.read(); // Process contents } catch(IOException e) { // Handle the error } } } ~~~ 在 Java 7 之前,try 后面总是跟着一个 {,但是现在可以跟一个带括号的定义 ——这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在`in`在整个 try 块的其余部分都是可用的。更重要的是,无论你如何退出 try 块(正常或通过异常),和以前的 finally 子句等价的代码都会被执行,并且不用编写那些杂乱而棘手的代码。这是一项重要的改进。 它是如何工作的? try-with-resources 定义子句中创建的对象(在括号内)必须实现`java.lang.AutoCloseable`接口,这个接口只有一个方法:`close()`。当在 Java 7 中引入`AutoCloseable`时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括`Stream`对象: ~~~ // exceptions/StreamsAreAutoCloseable.java import java.io.*; import java.nio.file.*; import java.util.stream.*; public class StreamsAreAutoCloseable { public static void main(String[] args) throws IOException{ try( Stream<String> in = Files.lines( Paths.get("StreamsAreAutoCloseable.java")); PrintWriter outfile = new PrintWriter( "Results.txt"); // [1] ) { in.skip(5) .limit(1) .map(String::toLowerCase) .forEachOrdered(outfile::println); } // [2] } } ~~~ * \[1\] 你在这里可以看到其他的特性:资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的)。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。 * \[2\] try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里,IOException 被 main() 方法抛出,所以这里并不需要在 try 后面跟着一个 catch 语句块。 Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。