Java io丑陋的try-finally块

Posted

技术标签:

【中文标题】Java io丑陋的try-finally块【英文标题】:Java io ugly try-finally block 【发布时间】:2011-02-11 13:19:34 【问题描述】:

是否有一种不那么丑陋的方式来处理close() 异常以关闭两个流:

    InputStream in = new FileInputStream(inputFileName);
    OutputStream out = new FileOutputStream(outputFileName);

    try 
        copy(in, out);
     finally 
        try 
            in.close();
         catch (Exception e) 
            try 
                // event if in.close fails, need to close the out
                out.close();
             catch (Exception e2) 
                throw e; // and throw the 'in' exception
            
        
        out.close();
    

更新:以上所有代码都在一个 try-catch 中,感谢您的警告。

终于(在答案之后):

使用Execute Around idiom 可以完成一个很好的实用方法(感谢 Tom Hawtin)。

【问题讨论】:

考虑在适当的抽象级别捕获异常,例如IOException等,而不是过于模糊的Exception。 我认为对于这种情况,抛出哪个异常无关紧要。或者为什么会? 请注意,您的代码不一定关闭in,见***.com/questions/2441853/… 另见:***.com/questions/12552863/… 【参考方案1】:

这是正确的惯用语(而且效果很好):

   InputStream in = null;
   OutputStream out = null;
   try 
       in = new FileInputStream(inputFileName);
       out = new FileOutputStream(outputFileName);
       copy(in, out);
   finally 
       close(in);
       close(out);
   

  public static void close(Closeable c) 
     if (c == null) return; 
     try 
         c.close();
      catch (IOException e) 
         //log the exception
     
  

之所以能正常工作,是因为在 finally 代码完成后会抛出在 finally 之前抛出的异常,前提是您的 finally 代码本身不会抛出异常或以其他方式异常终止。

编辑:从 Java 7(和 android SDK 19 - KitKat)开始,现在有一个 Try with resources 语法以使这个更干净。 this question 中解决了如何处理。

【讨论】:

注意:这不是所有流类型的正确习惯用法。如果 OutputStream 缓冲数据 (BufferedOutputStream) 或写入关闭块 (ZipOutputStream),您可能会丢失数据并且应用程序不会处理它,因为它会吞下异常。日志记录不能替代正确的错误处理。 @McDowell,关于输出流,这是一个很好的观点(请参阅我对 Adamski 的回答的评论,其中说了类似的话),但我仍然会说这是标准的成语。 因为这是公认的答案,人们会看它——既然你同意@McDowell,请在你的答案中说明他的评论——在catch块中 @Mr_and_Mrs_D,我不知道,随着 Java 6 生命周期的结束,真正的答案是使用 Java 7 选项,它肯定会很快。我不愿意在不同的前提下重新制作得到赞成和接受的答案。 是的 - 但大部分将继续是 Java 6 - Android 的一件事......【参考方案2】:

你可以实现一个实用方法:

public final class IOUtil 
  private IOUtil() 

  public static void closeQuietly(Closeable... closeables) 
    for (Closeable c : closeables) 
        if (c != null) try 
          c.close();
         catch(Exception ex) 
    
  

那么你的代码将被简化为:

try 
  copy(in, out);
 finally 
  IOUtil.closeQuietly(in, out);

附加

我想在第 3 方开源库中会有这样的方法。但是,我的偏好是避免不必要的库依赖,除非我使用它的大部分功能。因此,我倾向于自己实现这样的简单实用方法。

【讨论】:

这有点不同,因为 in 异常不会被重新抛出 好主意,一个 var-arg 的 closeables。 我会添加一个空检查。可能尚未分配可关闭资源。致 OP:Commons IO 库有类似的方法:commons.apache.org/io/api-1.4/org/apache/commons/io/… 你想“安静地”关闭输出流?! 如果只是被忽略,Java 开发人员为什么会引发异常?【参考方案3】:
try 
    final InputStream in = new FileInputStream(inputFileName);
    try 
        final OutputStream out = new FileOutputStream(outputFileName);    
        try 
            copy(in, out);
            out.flush(); // Doesn't actually do anything in this specific case.
         finally 
            out.close();
        
     finally 
        in.close();
    
 catch (IOException exc) 
    throw new SomeRelevantException(exc);

请记住,打开流可能会引发异常,因此您确实需要在流开口之间使用try(请不要做一些涉及nulls 的hack。任何东西都可能引发Error(这不是Exception) 的实例。

事实证明catchfinally 应该很少共享相同的try

从 Java SE 7 开始,您可以编写使用 try-with-resource 来避免太多缩进。尽管隐藏了被抑制的异常,但它或多或少做同样的事情。

try (
    final InputStream in = new FileInputStream(inputFileName);
    final OutputStream out = new FileOutputStream(outputFileName);    
) 
    copy(in, out);
    out.flush(); // Doesn't actually do anything in this specific case.
 catch (IOException exc) 
    throw new SomeRelevantException(exc);

您可能想使用Execute Around idiom。

我相信标准的良好复制方法是使用 NIO 的transferTo/transferFrom

【讨论】:

+1,这与我输入的答案相同(逐个字符)! :) 实际上非常好 - 与接受的答案相比,您能否评论一下它的好处?另外,如果说copy(in, out);out.close(); 都抛出,我们不会丢失copy(in, out); 抛出的异常吗? @Mr_and_Mrs_D 有点像。实际上,无缓冲 I/O 流不会从 close 抛出 IOException。即使他们确实抛出了IOException,控制流也是一样的。 /Java SE 7 添加了抑制异常。我不希望任何人搜索被抑制的树来找到正确的异常,这在本地甚至理论上都不可能做到,因为没有定义异常优先级的顺序。 谢谢 - 如果您发现 close() 异常并记录它,答案是否会更完整? 最佳答案在这里。您记得提到它在 Java 7 中要干净得多,您不必记住 close() 也可以抛出 RuntimeException(或任何其他 Throwable),并且该模式也适用于锁。 【参考方案4】:

我坚信在 Java 7.0 中,您不再需要自己显式关闭流。 Language Features in Java 7

try (BufferedReader br = new BufferedReader(new FileReader(path)) 
   return br.readLine();

【讨论】:

当它最终发布时,这将是了不起的。您可以在 try(...) 参数中指定多个资源,这些资源由 JVM 为您完全关闭。 虽然如果BufferedReader 构造函数失败(不太可能但确实发生)你会泄漏。此外,您正在选择随机配置的字符编码。【参考方案5】:

Guava 有非常好的 IO API,可以消除这种需求。例如,您的示例是:

Files.copy(new File(inputFileName), new File(outputFileName));

更一般地说,它使用InputSuppliers 和OutputSuppliers 的概念来允许在其实用程序方法中创建InputStreams 和OutputStreams,允许它完全控制它们以便它可以处理正确关闭。

此外,它还有Closeables.closeQuietly(Closeable),这基本上是大多数答案所建议的方法类型。

其中的 IO 内容仍处于测试阶段,可能会发生变化,但值得一试甚至使用,具体取决于您正在处理的内容。

【讨论】:

【参考方案6】:

Java 7 开始,对于 Closeable 资源,有一种更好的方式来编写 try-finally 块。

现在您可以在 try 关键字后面的括号中创建资源,如下所示:

try (initialize resources here) 
   ...

并且它们将在代码块完成后自动关闭。不需要finally 部分。

一个例子

try (
   ZipFile zf = new ZipFile(zipFileName);
   BufferedWriter writer = Files.newBufferedWriter(outputFilePath, charset);
) 
    // Enumerate each entry
    for (Enumeration entries = zf.entries(); entries.hasMoreElements();) 
        // Get the entry name and write it to the output file
        String newLine = System.getProperty("line.separator");
        String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
        writer.write(zipEntryName, 0, zipEntryName.length());
    

并且在for循环完成后,资源将被关闭!

【讨论】:

【参考方案7】:

在 commons io 中,IOUtils 有一些 closeQuietly 方法。

【讨论】:

【参考方案8】:

我有时使用的一个技巧是定义一个名为closeQuietly(Closeable) 的方法,该方法测试它的参数是否为null,然后关闭它,忽略任何异常。但是你需要小心地关闭 OutputStreams 和 Writers,因为它们实际上可能会抛出一个重要的异常;例如如果最终刷新失败。

Java 7 可能会有所改进。据报道,它将有一个新的结构,提供一种更简洁的方式来处理托管资源;例如完成后需要关闭的流。

最后,您应该知道您的示例存在错误。如果方法调用打开了第二个流,第一个流将不会被关闭。第二次打开需要在try 块内完成。

【讨论】:

【参考方案9】:

在大多数情况下,'in' close() 异常是无关紧要的,所以:

    try 
      copy(in, out);
     finally 
    try   in.close()    catch (Exception e)  /* perhaps log it */ 
    try   out.close()   catch (Exception e) /* perhaps log it */ 
     

吞下异常通常是不好的做法,但在这种情况下,我认为没关系。

【讨论】:

为什么忽略写出文件末尾的失败是可以的? 因为你知道它是一个ByteArrayOutputStream?【参考方案10】:

使用

IOUtils.closeNoThrow(myInputStream);

简洁优雅。

【讨论】:

可能你的意思是IOUtils.closeQuietly?【参考方案11】:

这是我的答案,希望好多了

https://***.com/a/35623998/2585433

try 
    fos = new FileOutputStream(new File("..."));
    bos = new BufferedOutputStream(fos);
    oos = new ObjectOutputStream(bos);

catch (Exception e) 

finally 
    Stream.close(oos,bos,fos);



class Stream 

public static void close(AutoCloseable... array) 
    for (AutoCloseable c : array) 
        try c.close();
        catch (IOException e) 
        catch (Exception e) 
    
   

【讨论】:

【参考方案12】:

在 C# 中,using 构造会在我们离开作用域时自动关闭可关闭对象:

using(Stream s = new Stream(filename)) 
  s.read();

我认为这是 java 的 try-finally 块的简写形式。 Java 6 引入了 Closable 接口。所以,using 就快到了。当最后一步在 Java 7 中完成时,它确实很棒。

【讨论】:

以上是关于Java io丑陋的try-finally块的主要内容,如果未能解决你的问题,请参考以下文章

Java,try-finally 没有捕获

Java中trycatchfinally执行顺序

java7与java9中的try-finally关闭资源

java7与java9中的try-finally关闭资源

tryresources优于try-finally

第9项:尽量使用try-with-resources而不是try-finally(Prefer try-with-resources to try-finally)