💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### [揭示细节](https://lingcoder.gitee.io/onjava8/#/book/15-Exceptions?id=%e6%8f%ad%e7%a4%ba%e7%bb%86%e8%8a%82) 为了研究 try-with-resources 的基本机制,我们将创建自己的 AutoCloseable 类: ~~~ // exceptions/AutoCloseableDetails.java class Reporter implements AutoCloseable { String name = getClass().getSimpleName(); Reporter() { System.out.println("Creating " + name); } public void close() { System.out.println("Closing " + name); } } class First extends Reporter {} class Second extends Reporter {} public class AutoCloseableDetails { public static void main(String[] args) { try( First f = new First(); Second s = new Second() ) { } } } ~~~ 输出为: ~~~ Creating First Creating Second Closing Second Closing First ~~~ 退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在这种情况下,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。 假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象 ~~~ // exceptions/TryAnything.java // {WillNotCompile} class Anything {} public class TryAnything { public static void main(String[] args) { try( Anything a = new Anything() ) { } } } ~~~ 正如我们所希望和期望的那样,Java 不会让我们这样做,并且出现编译时错误。 如果其中一个构造函数抛出异常怎么办? ~~~ // exceptions/ConstructorException.java class CE extends Exception {} class SecondExcept extends Reporter { SecondExcept() throws CE { super(); throw new CE(); } } public class ConstructorException { public static void main(String[] args) { try( First f = new First(); SecondExcept s = new SecondExcept(); Second s2 = new Second() ) { System.out.println("In body"); } catch(CE e) { System.out.println("Caught: " + e); } } } ~~~ 输出为: ~~~ Creating First Creating SecondExcept Closing First Caught: CE ~~~ 现在资源规范头中定义了 3 个对象,中间的对象抛出异常。因此,编译器强制我们使用 catch 子句来捕获构造函数异常。这意味着资源规范头实际上被 try 块包围。 正如预期的那样,First 创建时没有发生意外,SecondExcept 在创建期间抛出异常。请注意,不会为 SecondExcept 调用 close(),因为如果构造函数失败,则无法假设你可以安全地对该对象执行任何操作,包括关闭它。由于 SecondExcept 的异常,Second 对象实例 s2 不会被创建,因此也不会有清除事件发生。 如果没有构造函数抛出异常,但在 try 的主体中可能抛出异常,那么你将再次被强制要求提供一个catch 子句: ~~~ // exceptions/BodyException.java class Third extends Reporter {} public class BodyException { public static void main(String[] args) { try( First f = new First(); Second s2 = new Second() ) { System.out.println("In body"); Third t = new Third(); new SecondExcept(); System.out.println("End of body"); } catch(CE e) { System.out.println("Caught: " + e); } } } ~~~ 输出为: ~~~ Creating First Creating Second In body Creating Third Creating SecondExcept Closing Second Closing First Caught: CE ~~~ 请注意,第 3 个对象永远不会被清除。那是因为它不是在资源规范头中创建的,所以它没有被保护。这很重要,因为 Java 在这里没有以警告或错误的形式提供指导,因此像这样的错误很容易漏掉。实际上,如果依赖某些集成开发环境来自动重写代码,以使用 try-with-resources 特性,那么它们(在撰写本文时)通常只会保护它们遇到的第一个对象,而忽略其余的对象。 最后,让我们看一下抛出异常的 close() 方法: ~~~ // exceptions/CloseExceptions.java class CloseException extends Exception {} class Reporter2 implements AutoCloseable { String name = getClass().getSimpleName(); Reporter2() { System.out.println("Creating " + name); } public void close() throws CloseException { System.out.println("Closing " + name); } } class Closer extends Reporter2 { @Override public void close() throws CloseException { super.close(); throw new CloseException(); } } public class CloseExceptions { public static void main(String[] args) { try( First f = new First(); Closer c = new Closer(); Second s = new Second() ) { System.out.println("In body"); } catch(CloseException e) { System.out.println("Caught: " + e); } } } ~~~ 输出为: ~~~ Creating First Creating Closer Creating Second In body Closing Second Closing Closer Closing First Caught: CloseException ~~~ 从技术上讲,我们并没有被迫在这里提供一个 catch 子句;你可以通过**main() throws CloseException**的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。 请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer.close() 抛出异常也是如此。仔细想想,这就是你想要的结果。但如果你必须亲手编写所有的逻辑,或许会丢失一些东西并使得逻辑出错。想想那些程序员没有考虑 Clean up 的所有影响并且出错的代码。因此,如果可以,你应当始终使用 try-with-resources。这个特性有助于生成更简洁,更易于理解的代码。