java.io.Closeable
interface. So, immediately to the point.OutputStream
. Task: get the OutputStream
as input, do some useful work with it, close the OutputStream
. OutputStream stream = openOutputStream(); // - stream stream.close();
try-finally
: OutputStream stream = openOutputStream(); try { // - stream } finally { stream.close(); }
close()
will always be called (for finally
): the resource will be freed anyway. It seems everything is correct. It is so?close()
method can throw an exception. And if the main code for working with the resource also throws an exception, it will be overwritten with an exception from close()
. The information about the original error disappears: we will never know what caused the original exception.stream.close()
can wipe out the “main” exception, then let's just “swallow” the exception from close()
: OutputStream stream = openOutputStream(); try { // - stream } finally { try { stream.close(); } catch (Throwable unused) { // } }
close()
. Suppose that outputStream
is a FileOutputStream
wrapped in a BufferedOutputStream
. Since BufferedOutputStream
does flush()
on the underlying stream in chunks, there is a possibility that it will call it during a call to close()
. Now imagine that the file we are writing to is locked. Then the close()
method will throw an IOException
that will be successfully “eaten”. Not a single byte of user data was written to the file, and we did not know anything about it. Information is lost.InputStream
used instead of the OutputStream
, then such code has the right to life. The fact is that if an exception is thrown in InputStream.close()
, then (most likely) there will be no bad consequences, since we already considered everything we wanted from this stream. This means that InputStream
and OutputStream
have completely different semantics.close()
method. It looks like this: OutputStream stream = openOutputStream(); Throwable mainThrowable = null; try { // - stream } catch (Throwable t) { // mainThrowable = t; // throw t; } finally { if (mainThrowable == null) { // . close() stream.close(); } else { try { stream.close(); } catch (Throwable unused) { // , // ( ) } } }
close()
disappears if the main code throws an exception. Also openOutputStream()
can return null
, and then a NullPointerException
will NullPointerException
(solved by adding another if'a, which leads to even more cumbersome code). Finally, if we have two resources (for example, InputStream
and OutputStream
) and more, then the code will simply be unbearably complex.try-with-resources
construct. We use it: try (OutputStream stream = openOutputStream()) { // - stream }
close()
method, the first exception will be prioritized, and the second exception will be suppressed, but information about it will be saved (using the Throwable.addSuppressed(Throwable exception)
method, which is implicitly called by the Java compiler): Exception in thread "main" java.lang.RuntimeException: Main exception at A$1.write(A.java:16) at A.doSomething(A.java:27) at A.main(A.java:8) Suppressed: java.lang.RuntimeException: Exception on close() at A$1.close(A.java:21) at A.main(A.java:9)
com.google.common.io.Closer
( try-with-resources
for the poor) appeared, with which you can simplify the non-ideal solution above: Closer closer = Closer.create(); try { OutputStream stream = closer.register(openOutputStream()); // - stream } catch (Throwable e) { // ( Error') throw closer.rethrow(e); } finally { closer.close(); }
Closer
also supports an arbitrary amount of resources in it (the register(...)
method). Unfortunately, Closer
is a class marked with the @Beta
annotation, which means it may undergo significant changes in future versions of the library (up to removal).InputStream
and OutputStream
( Reader
and Writer
) are handled differently (at least in Java 6)!NullPointerException
.Source: https://habr.com/ru/post/178405/
All Articles