Why does PrintStream.close() end up getting called twice?
-
21-08-2019 - |
Question
Somewhat to my surprise, the following code prints out "Close" twice. Running through the debugger, it seems MyPrintStream.close()
calls super.close()
, which ends up calling MyPrintStream.close()
again.
import java.io.*;
public class PrintTest
{
static class MyPrintStream extends PrintStream
{
MyPrintStream(OutputStream os)
{
super(os);
}
@Override
public void close()
{
System.out.println("Close");
super.close();
}
}
public static void main(String[] args) throws IOException
{
PrintStream ps = new MyPrintStream(new FileOutputStream(File.createTempFile("temp", "file")));
ps.println("Hello");
ps.close();
}
}
Why is this happening? Should I not be extending PrintStream?
Solution
Take a look at PrintStream's source.
It has two references to the underlying Writer textOut
and charOut
, one character-base, and one text-based (whatever that means). Also, it inherits a third reference to the byte-based OutputStream, called out
.
/**
* Track both the text- and character-output streams, so that their buffers
* can be flushed without flushing the entire stream.
*/
private BufferedWriter textOut;
private OutputStreamWriter charOut;
In the close()
method it closes all of them (textOut
is basically the same as charOut
).
private boolean closing = false; /* To avoid recursive closing */
/**
* Close the stream. This is done by flushing the stream and then closing
* the underlying output stream.
*
* @see java.io.OutputStream#close()
*/
public void close() {
synchronized (this) {
if (! closing) {
closing = true;
try {
textOut.close();
out.close();
}
catch (IOException x) {
trouble = true;
}
textOut = null;
charOut = null;
out = null;
}
}
}
Now, the interesting part is that charOut contains a (wrapped) referenced to the PrintStream itself (note the init(new OutputStreamWriter(this))
in the constructor )
private void init(OutputStreamWriter osw) {
this.charOut = osw;
this.textOut = new BufferedWriter(osw);
}
/**
* Create a new print stream.
*
* @param out The output stream to which values and objects will be
* printed
* @param autoFlush A boolean; if true, the output buffer will be flushed
* whenever a byte array is written, one of the
* <code>println</code> methods is invoked, or a newline
* character or byte (<code>'\n'</code>) is written
*
* @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean)
*/
public PrintStream(OutputStream out, boolean autoFlush) {
this(autoFlush, out);
init(new OutputStreamWriter(this));
}
So, the call to close()
will call charOut.close()
, which in turn calls the original close()
again, which is why we have the closing flag to cut short the infinite recursion.
OTHER TIPS
If you look at your code in a debugger and set a breakpoint in the close()
method, it'll reveal the stacktraces of who is calling your close()
method:
- your main method
- sun.nio.cs.StreamEncoder$CharsetSE.implClose() line 431
the complete stacktrace for the latter looks like:
PrintTest$MyPrintStream.close() line: 20
sun.nio.cs.StreamEncoder$CharsetSE.implClose() line: 431 [local variables unavailable]
sun.nio.cs.StreamEncoder$CharsetSE(sun.nio.cs.StreamEncoder).close() line: 160 [local variables unavailable]
java.io.OutputStreamWriter.close() line: 222 [local variables unavailable]
java.io.BufferedWriter.close() line: 250 [local variables unavailable]
PrintTest$MyPrintStream(java.io.PrintStream).close() line: 307
PrintTest$MyPrintStream.close() line: 20
PrintTest.main(java.lang.String[]) line: 27
Sadly though I can't tell why StreamEncoder would call back into your PrintStream though, as my IDE doesn't have a source attachment for sun.nio.cs.StreamEncoder :( This is JDK 6 btw, if that matters.
By the way, if you are asking this question because you noticed that custom code in your close()
method is running twice, you should really be checking if this.closing
. PrintStream.close()
sets this to true (and the class's comments state /* To avoid recursive closing */
).