正确的方式来closures在Java中的嵌套stream和作家

注意:这个问题和它的大部分答案都在Java 7发布之前。Java 7提供了自动资源pipe理function,可以很容易地完成这个任务。 如果您使用的是Java 7或更高版本,那么您应该继续讨论Ross Johnson的答案 。


什么被认为是最好的,最全面的方式来closuresJava中的嵌套stream? 例如,考虑设置:

FileOutputStream fos = new FileOutputStream(...) BufferedOS bos = new BufferedOS(fos); ObjectOutputStream oos = new ObjectOutputStream(bos); 

我知道密切的操作需要被保险(可能通过使用finally子句)。 我想知道的是,是否有必要明确确定嵌套stream是封闭的,还是足够确保closures外部stream(oos)?

有一点我注意到,至less在处理这个特定的例子时,内部stream只能抛出FileNotFoundExceptions。 这似乎意味着,从技术上讲没有必要担心如果失败就closures它们。

这是一个同事写的:


从技术上讲,如果实施得当,closures最外层的stream(oos)就足够了。 但是实施似乎有缺陷。

例如:BufferedOutputStream从FilterOutputStreaminheritanceclose(),它定义为:

  155 public void close() throws IOException { 156 try { 157 flush(); 158 } catch (IOException ignored) { 159 } 160 out.close(); 161 } 

但是,如果由于某种原因flush()抛出运行时exception,那么out.close()永远不会被调用。 所以,似乎“最安全”(但丑陋),主要是担心closuresFOS,这是保持文件打开。


什么被认为是最好的,当你绝对需要确定,closures嵌套stream的方法?

有没有官方的Java / Sun文档详细处理这个问题?

我通常做以下。 首先,定义一个基于模板方法的类来处理try / catch混乱

 import java.io.Closeable; import java.io.IOException; import java.util.LinkedList; import java.util.List; public abstract class AutoFileCloser { // the core action code that the implementer wants to run protected abstract void doWork() throws Throwable; // track a list of closeable thingies to close when finished private List<Closeable> closeables_ = new LinkedList<Closeable>(); // give the implementer a way to track things to close // assumes this is called in order for nested closeables, // inner-most to outer-most protected final <T extends Closeable> T autoClose(T closeable) { closeables_.add(0, closeable); return closeable; } public AutoFileCloser() { // a variable to track a "meaningful" exception, in case // a close() throws an exception Throwable pending = null; try { doWork(); // do the real work } catch (Throwable throwable) { pending = throwable; } finally { // close the watched streams for (Closeable closeable : closeables_) { if (closeable != null) { try { closeable.close(); } catch (Throwable throwable) { if (pending == null) { pending = throwable; } } } } // if we had a pending exception, rethrow it // this is necessary b/c the close can throw an // exception, which would remove the pending // status of any exception thrown in the try block if (pending != null) { if (pending instanceof RuntimeException) { throw (RuntimeException) pending; } else { throw new RuntimeException(pending); } } } } } 

请注意“待处理”exception – 这将处理在closures期间抛出的exception会掩盖我们可能真正关心的exception的情况。

finally试图从任何装饰stream的外部closures,所以如果你有一个BufferedWriter包装一个FileWriter,我们首先closuresBuffereredWriter,如果失败,仍然尝试closuresFileWriter本身。 (请注意,如果stream已经closures,则Closeable调用close()来忽略调用的定义)

你可以使用上面的类如下:

 try { // ... new AutoFileCloser() { @Override protected void doWork() throws Throwable { // declare variables for the readers and "watch" them FileReader fileReader = autoClose(fileReader = new FileReader("somefile")); BufferedReader bufferedReader = autoClose(bufferedReader = new BufferedReader(fileReader)); // ... do something with bufferedReader // if you need more than one reader or writer FileWriter fileWriter = autoClose(fileWriter = new FileWriter("someOtherFile")); BufferedWriter bufferedWriter = autoClose(bufferedWriter = new BufferedWriter(fileWriter)); // ... do something with bufferedWriter } }; // .. other logic, maybe more AutoFileClosers } catch (RuntimeException e) { // report or log the exception } 

使用这种方法,你永远不用担心try / catch / finally来处理再次closures文件。

如果这太重了,至less要考虑try / catch和它使用的“pending”variables的方法。

closures链式stream时,只需closures最外层的stream。 任何错误都会在链条上传播并被捕获。

有关详细信息,请参阅Java I / Ostream 。

解决这个问题

但是,如果由于某种原因flush()抛出运行时exception,那么out.close()永远不会被调用。

这是不对的。 在捕获并忽略该exception之后,执行catch块和out.close()语句将执行。

您的同事对运行时exception做了很好的说明。 如果您绝对需要closuresstream,您可以尝试从外部单独closures每一个,停止第一个exception。

在Java 7时代, 尝试与资源无疑是最好的select。 如前几个答案中所提到的那样,closures请求从最外层的stream传播到最内层的stream。 所以只需要一个closures就可以了。

 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) { // do something with ois } 

然而,这种模式有一个问题。 try-with-resources不知道内部FileInputStream,所以如果ObjectInputStream构造函数抛出一个exception,FileInputStream从不closures(直到垃圾回收器到达它)。 解决scheme是…

 try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) { // do something with ois } 

这不是优雅,但更强大。 这实际上是否是一个问题将取决于在构build外部对象时可能抛出什么exception。 ObjectInputStream可以抛出IOExceptionexception,这可能会被应用程序处理而不终止。 许多stream类只会抛出未经检查的exception,这可能会导致应用程序终止。

使用Apache Commons处理IO相关的对象是一个很好的习惯。

finally子句中使用IOUtils

IOUtils.closeQuietly(bWriter); IOUtils.closeQuietly(oWritter);

下面的代码片段。

 BufferedWriter bWriter = null; OutputStreamWriter oWritter = null; try { oWritter = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" ); bWriter = new BufferedWriter( oWritter ); bWriter.write( xml ); } finally { IOUtils.closeQuietly(bWriter); IOUtils.closeQuietly(oWritter); } 

这位同事提出了一个有趣的观点,有理由争论这两种方式。

就个人而言,我会忽略RuntimeException ,因为未经检查的exception表示程序中的错误。 如果程序不正确,请修复它。 你不能在运行时“处理”坏的程序。

这是一个令人惊讶的尴尬问题。 (即使假设acquire; try { use; } finally { release; }代码是正确的。

如果装饰器的构造失败,那么你将不会closures基础stream。 因此,您需要明确地closures底层stream,无论是在最终使用之后,还是在成功将资源交给装饰器后更多的diifcult)。

如果一个exception导致执行失败,你真的想刷新吗?

一些装饰器本身实际上拥有资源。 ZipInputStream的当前Sun实现例如分配了非Java堆内存。

据称(IIRC)三分之二的资源在Java库中的使用是以明显不正确的方式实现的。

BufferedOutputStream即使在flush IOException也会closures,但BufferedWriter正确closures。

我的build议是:尽可能直接closures资源,不要让它们污染其他代码。 OTOH,你可以在这个问题上花太多时间 – 如果OutOfMemoryError ,performance得很好,但是你的程序的其他方面可能是更高的优先级,而且在这种情况下,库代码可能会被破坏。 但我总是写:

 final FileOutputStream rawOut = new FileOutputStream(file); try { OutputStream out = new BufferedOutputStream(rawOut); ... write stuff out ... out.flush(); } finally { rawOut.close(); } 

(看:没有赶上!)

也许使用Execute Around成语。

你也不必closures所有的嵌套stream

检查这个http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

Java SE 7 试用资源似乎没有被提及。 它消除了需要完全明确地closures,我非常喜欢这个想法。

不幸的是,对于Android开发,只有使用Android Studio(我认为)以及针对Kitkat和更高版本才能获得这种甜蜜。

我用这种方式来closuresstream, 而不是在finally块中嵌套try-catch

 public class StreamTest { public static void main(String[] args) { FileOutputStream fos = null; BufferedOutputStream bos = null; ObjectOutputStream oos = null; 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) {} } } } 

Sun的JavaDocs在其文档中包含RuntimeException ,如InputStream的read(byte [],int,int)方法所示。 logging为抛出NullPointerException和IndexOutOfBoundsException。

FilterOutputStream的flush()仅被logging为抛出IOException,因此实际上并不会抛出任何RuntimeException 。 任何可能被抛出的东西都可能被封装在一个IIOException

它仍然可以抛出一个Error ,但是对于这些Error你可以做的不多。 Sunbuild议你不要试图抓住它们。