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?

Was it helpful?

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:

  1. your main method
  2. 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 */ ).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top