正确的方式来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议你不要试图抓住它们。