【问题标题】:Java io ugly try-finally blockJava io丑陋的try-finally块
【发布时间】: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)。

【问题讨论】:

标签: java file-io try-catch


【解决方案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);

          简洁优雅。

          【讨论】:

          【解决方案11】:

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

          https://stackoverflow.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 中完成时,它确实很棒。

            【讨论】:

              猜你喜欢
              • 2015-09-05
              • 1970-01-01
              • 2011-06-01
              • 1970-01-01
              • 2014-01-08
              • 1970-01-01
              • 2017-07-20
              • 2011-02-13
              • 1970-01-01
              相关资源
              最近更新 更多