views:

298

answers:

2

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?

+11  A: 

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 */ ).

matt b
+1 for educating the user how to solve this by himself in the future
Aaron Digulla
The 'closing' instance variable is private to PrintStream, so I can't check it, though of course I can use my own.
Simon Nickerson
There are a couple of classes in the jdk which do something similar. I believe it is because there are situations where classes A and B reference each other, and the user may have a reference to either A or B, and closing either of them should close the other. As mentioned, you should generally protect your close methods from multiple invocations (although recursive invocation is a more insidious and less expected situation).
james
Great answer. simonn - I think the suggestion was to add your own closing instance variable if you need it.
Kevin Day
+1  A: 

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.

Thilo