AutoCloseable接口的close方法抛出异常是否有意义?应该如何处理?

3

在C#中,在IDisposableDispose方法中抛出异常被认为是不良做法(considered)(bad practice)

相比之下,在Java中,AutoCloseableclose方法允许抛出任何异常,并强制调用者以某种方式处理它。但如果发生这种情况,调用者可以合理地期望做什么呢?这表明尝试关闭资源失败了。用户是否需要再次尝试关闭资源,或许使用某种指数退避策略?


尝试这个:https://dev59.com/vW445IYBdhLWcg3w1tcS - Adam Siemion
3个回答

1
AutoCloseable 的设计是 Java 受检异常的结果。一些实现必须能够抛出受检异常,因此需要 throws Exception。然而,如果有的话,实现应该声明更具体的异常类型

虽然这个接口方法被声明为抛出 Exception,但强烈建议实现者声明 close 方法的具体实现抛出更具体的异常,或者如果关闭操作不可能失败,则不抛出任何异常。

如果有避免抛出异常的方法,就不应该抛出异常,但并不总是可以避免。例如,在关闭带有未刷新数据的 BufferedOutputStream 时,缓冲流有两个选项;忽略未写入的数据并关闭,或将其写入流中,这可能会导致抛出异常。


1
由于Java的设计者在实现自己的try-with-resources功能之前能够看到.NET中清理异常处理中出现的问题,他们得以对其进行改进。在.NET中,Dispose块的作者通常面临一个不愉快的选择,即吞噬任何发生的异常,从而错误地让调用程序认为一切正常,或者让异常从Dispose中传播,从而抹掉任何先前异常的证据。幸运的是,Java避免了这个问题。
如果try-with-resources块正常成功并且close也正常成功,那么外部代码将看到一切正常。如果在try部分发生异常但close正常成功,则外部代码将看到try-block异常。如果try正常完成但close抛出异常,则外部代码将看到close异常。如果try抛出异常,但close也抛出异常,则外部代码将看到try异常,但也能检索到close中发生的任何异常(如果多个嵌套的try-with-resources在close期间抛出异常,则所有抛出的异常都将对外部代码可用)。
因此,与.NET设计不同,后者经常迫使作者压制由Dispose引发的一些潜在严重异常,Java的设计倾向于让close在任何时候都抛出异常,以便调用者不应该相信一切都很好。

在Java中,这些被称为“抑制异常”,Throwable具有访问器来处理它们。请参阅https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html中的“抑制异常”部分。 - SensorSmith
@SensorSmith:随意编辑注释中的细节。我的Java经验早于try-with-resources,所以我宁愿让更有资格的人来撰写这样的注释。 - supercat

0

看起来涉及资源的每个操作,包括隐式的close()调用,都被视为try{}块的一部分。即使从技术上/语法上讲,资源是在{}大括号之外提到的。

这意味着如果在close()期间抛出IOException,它将被与您的try相关联的某个catch()子句捕获(或者它将向上传播)。

关于为什么可能需要抛出异常的原因:close()可能会导致flush(),flush()可能会导致write()失败。


如果我有以下代码:try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); }并且 br.close() 抛出异常,则我将无法在必须处理该异常的地方再次尝试关闭资源。这似乎表明不需要再次尝试关闭资源。 - George Simms
是的。另一种思考方式是,虽然我们习惯于使用try-catch-finally来编写代码,在隐式关闭中更像是try-finally-catch - 清理代码在catch之前执行,并且可能会生成一个异常,catch可能希望处理该异常。 - Concrete Gannet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接