如何从close()正确处理IOException
Java I / O类java.io.Reader
, java.io.Writer
, java.io.InputStream
, java.io.OutpuStream
及其各种子类都有一个close()
方法,可以抛出IOException
。
对于处理此类例外的正确方法是否有任何共识?
我经常看到建议只是默默地忽略它们,但这感觉不对,至少在开放写入资源的情况下,关闭文件时出现问题可能意味着无法写入/发送未刷新的数据。
另一方面,在阅读资源时,我完全不清楚为什么close()
可能抛出以及该怎么做。
那么有任何标准建议吗?
一个相关的问题是, close是否会抛出IOException? ,但更多的是关于哪些实现真正抛出,而不是如何处理exception。
记录下来。
你无法真正做任何事情 (例如写一些从错误中恢复的代码),但它通常值得让人知道。
编辑:
在进一步调查并阅读其他评论之后,我会说如果你确实想要处理它,那么你将不得不知道实施的细节。 相反,您可能需要了解实现的详细信息,以确定是否需要处理它。
但实际上,我无法想到任何流的示例,其中读取或写入将正常工作而不会抛出exception,但结束会。
“忽略或只是记录通常是一个坏主意。” 那不是回答这个问题。 关于close()的IOException应该怎么办? 只是重新抛出它只会进一步推动问题,在那里处理起来更加困难。
理论
直接回答您的问题:当此IO操作失败时,您可能需要根据具体情况
- 回滚更改(不要在磁盘上留下部分写入的文件,删除它)
- 重试(可能在回滚后)
- 忽略(并继续)
- 中断当前任务(取消)
您经常可以看到显示给用户的最后3个对话框。 实际上,将选择委托给用户是可能的。
我认为重点是不要让系统处于不一致的状态。 只是吞下一个接近的exception可能会让你留下一个残缺的文件,以后会导致令人讨厌的错误。
实践
使用已检查的exception有点麻烦。 选项出现:
-
将它们转换为
RuntimeException
,抛出它们并使它们处于足够高的水平 -
继续使用已检查的exception并遭受句法上的痛苦
-
使用更多可组合的error handling结构,比如IO monad(在Java中不是真的可用,至少不是没有非常多的大括号(参见http://apocalisp.wordpress.com/2008/05/16/thrower-functor/ )而不是充满力量)
更多关于IO monad
在IO monad上下文中返回结果时,实际上并不是在执行IO操作,而是返回该操作的描述。 为什么? 这些动作有一个API,它们可以很好地组合(与throws / try / catch大屠杀相比)。
您可以决定是否要检查exception样式处理( Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
使用Option
或Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
unsafePerformIO`调用(实际执行组合的IO操作) Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
。
要了解一些信息,请参阅我的Scala要点http://gist.github.com/2709535 ,它承载了executeAndClose
方法。 它返回资源处理的结果(如果可用)和未关闭的资源(如果关闭失败)。 然后,用户决定如何处理这种不可关闭的资源。
如果需要一个/多个实际exception,可以使用Validation
/ ValidationNEL
而不是Option
来增强它。
调用InputStream.close()
或OutputStream.close()
代码是一些高级代码,它委托给较低级别的InputStream
或OutputStream
类来执行某些高级计算。
是否抛出exception
你只有3个选择。
- 抓住并吞下exception,丢弃它。
- 让它向上传播。
- 捕获exception,并抛出另一种exception。 在这种情况下,您通常会使用链式exception 。
最后两个选项类似,因为您的代码会抛出exception。 所以这个问题实际上是我的代码何时抛出exception的问题的一个特例 。 在这种情况下,该问题的正确答案是:当且仅当替代方案不符合代码的后置条件或维护代码的不变量时 。 post条件指定了您的方法的用途。 不变量指定了类的特征。
所以,你需要问自己, close()
抛出exception是否会阻止你的方法做应该做的事情。
- 如果它不能阻止你的方法完成它的工作,那么正确的做法是吞下exception。 尽管许多人会告诉你。
- 如果
close()
throw确实阻止了你的方法完成它的工作,并且该方法可能抛出IOException
,你就什么都不做, 只是让exception向上传播 。 - 如果
close()
阻止您的方法执行其工作,但该方法可能不会抛出IOException
,则必须捕获IOException
并将其重新抛出为另一类exception,将IOException
记录为抛出exception的原因。
我知道抛出exception的InputStream.close()
在任何情况下都不能阻止计算成功。 在您完成阅读您感兴趣的任何数据后,对close()
调用自然会发生。
但是,输出流可以在内部(在Java代码内)或在较低级别(在操作系统内)缓冲要写入输出目的地的数据。 因此,在OutputStream.close()
成功返回( 不抛出exception)之前,您无法确定对输出流的写操作是否实际产生了实际输出。 因此,您应该像处理写入失败一样处理OutputStream.close()
抛出的exception。
您的方法负责维护其不变量。 如果close()
抛出exception,有时需要清理或回滚操作。 您应该将其代码放在IOException
的catch
或finally
子句中,即使您希望exception向上传播也是如此。 如果使用catch子句并且想要传播它,则必须重新抛出它。
是否记录exception
有些人会建议您记录exception 。 这几乎总是糟糕的建议。 日志消息是程序用户界面的一部分。 从根本上说,你必须总是问自己是否要记录任何内容 ,因为无用的措辞可能会分散用户的注意力并使阅读日志文件的用户感到困惑(“用户”包括系统管理员)。 记录的每条消息都应该用于某些有用的目的。 每个人都应提供有助于用户做出决定的信息 。
具体报告close()
失败很少有用。 它如何帮助用户做出决定? 如果exception没有阻止您的方法执行其工作,则没有问题,并且不需要用户执行任何操作。 如果您的程序无法close()
流,这是一个问题,用户可以做些什么来帮助?
低级代码通常不负责日志记录。 它改为执行抽象操作,通过抛出exception向程序的更高级别部分报告失败。 关闭流的代码通常是相当低的级别,因此检测到close()
的代码级别太低而无法执行任何日志记录。
close()
失败的具体事实很少有用。 可能有用的是知道您的方法要执行的抽象操作失败。 这可以通过让更高级别的代码捕获所有预期的exception并报告操作失败,而不是让您的方法精确报告“关闭失败”来完成。
在关闭writer之前尝试使用flush。 关闭exception的主要原因是某些其他资源可能一直在使用数据,或者编写器/阅读器可能根本不打开。 在关闭之前尝试找到资源已经打开的东西。
首先,不要忘记将close()放在try catch块的“finally”部分中。 其次,您可以使用另一个try catchexception包围close方法并记录exception。
这取决于你关闭的是什么。 例如,关闭StringWriter
什么都不做。 javadocs很清楚。 在这种情况下,您可以忽略IOException,因为您知道它永远不会生成。 从技术上讲,你甚至不需要打电话。
/** * Closing a StringWriter has no effect. The methods in this * class can be called after the stream has been closed without generating * an IOException. */ public void close() throws IOException { }
对于其他流,请根据需要记录并处理exception。
通常资源处理应如下所示:
final Resource resource = context.acquire(); try { use(resource); } finally { resource.release(); }
在(不太可能) release
抛出exception的情况下,使用抛出的任何exception都将被删除。 我最接近捕手获胜的例外没有问题。 我相信JDK7中的ARM块(?)在这种情况下会做一些疯狂的事情,比如重新抛出附加的release
例外的use
exception。
如果您使用Execute Around惯用法,您可以将这些决策和可能混乱的代码放在一个(或几个)位置。
忽略或只是记录通常是一个坏主意。 尽管您无法从中恢复,但应始终以统一的方式处理I / Oexception,以确保您的软件以统一的方式运行。
我至少建议处理如下:
//store the first exception caught IOException ioe = null; Closeable resource = null; try{ resource = initializeResource(); //perform I/O on resource here }catch(IOException e){ ioe = e; }finally{ if (resource != null){ try{ resource.close(); }catch(IOException e){ if(null == ioe){ //this is the first exception ioe = e; }else{ //There was already another exception, just log this one. log("There was another IOException during close. Details: ", e); } } } } if(null != ioe){ //re-throw the exception to the caller throw ioe; }
上面的内容非常冗长,但效果很好,因为在资源I / O期间它会IOException
于关闭期间的IOException
,因为它更可能包含开发人员感兴趣的信息。 当出现问题时,代码也会抛出IOException
,因此您可以获得统一的行为。
可能有更好的方法,但你明白了。
理想情况下,您将创建一个允许存储兄弟exception的新exception类型,因此,如果您获得两个IOException
,则可以将它们存储起来。
好吧,在大多数情况下,close()实际上不会抛出IOException。 这是InputStream.java的代码:
public void close() throws IOException { // Do nothing }
关闭网络资源的错误应该是某种类型的RuntimeException
,因为您可以在程序连接到网络资源后断开连接。
您可以使用Google Code Search查看Reader / Writer和Streams的各种实现的一些示例。 BufferedReader和PipedReader都没有实际抛出IOException,所以我认为你大部分时间都不会担心它。 如果您真的很担心,可以检查您正在使用的库的实现,看看是否需要担心exception。
与其他提到的一样,除了记录IOException之外,你无法做很多事情。
毕竟, finally
子句中的try/catch blocks
非常难看。
编辑:
进一步检查会显示IOException
子类,如InterruptedIOException
, SyncFailedException
和ObjectStreamException
,以及从中inheritance的类。 所以只是捕获一个IOException
太普遍了 – 你不会知道如何处理除了记录之外的信息,因为它可能来自一系列错误。
编辑2:
Urk, BufferedReader
是一个糟糕的例子,因为它需要一个Reader
作为输入。 我已将其更改为InputStream.java
但是,有一个层次结构,其中InputStream
<= FilterInputStream
<= BufferedInputStream
<= InputStreamReader
(通过inheritance和私有实例),这些层次结构都是在InputStream
的close()
方法。