为什么PrintStream.close()最终会被调用两次?

Sim*_*son 7 java inheritance printstream

令我惊讶的是,下面的代码打印出两次"关闭".通过调试器,它似乎MyPrintStream.close()调用super.close(),最终MyPrintStream.close()再次调用.

    
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();
    }
}

为什么会这样?我不应该扩展PrintStream吗?

mat*_*t b 11

如果您在调试器中查看代码并在close()方法中设置断点,它将显示正在调用您的close()方法的堆栈跟踪:

  1. 你的主要方法
  2. sun.nio.cs.StreamEncoder $ CharsetSE.implClose()第431行

后者的完整堆栈跟踪看起来像:

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 
Run Code Online (Sandbox Code Playgroud)

可悲的是,虽然我不知道为什么 StreamEncoder会回调到你的PrintStream,因为我的IDE没有sun.nio.cs.StreamEncoder的源附件:(这是JDK 6顺便说一句,如果这很重要.

顺便说一句,如果你问这个问题,因为你发现方法中的自定义代码close()运行了两次,你应该检查是否this.closing.PrintStream.close()将此设置为true(以及类的注释状态/* To avoid recursive closing */).

  • jdk中有几个类可以做类似的事情.我认为这是因为有些情况下A类和B类相互引用,并且用户可能引用了A或B,而关闭其中任何一个都应该关闭另一个.如前所述,您通常应该保护您的close方法免受多次调用(尽管递归调用是一种更加隐蔽且不太预期的情况). (2认同)

Thi*_*ilo 1

看一下PrintStream的源码。

它有两个对底层 WritertextOut和的引用charOut,一个基于字符,一个基于文本(无论这意味着什么)。此外,它还继承了对基于字节的 OutputStream 的第三个引用,称为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;
Run Code Online (Sandbox Code Playgroud)

close()方法中它关闭了所有这些(textOut与基本相同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;
    }
}
}
Run Code Online (Sandbox Code Playgroud)

现在,有趣的部分是 charOut 包含一个(包装的)引用 PrintStream 本身(注意init(new OutputStreamWriter(this))构造函数中的 )

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));
}
Run Code Online (Sandbox Code Playgroud)

因此,对close()will call的调用charOut.close(),又会再次调用原始函数close(),这就是为什么我们有结束标志来缩短无限递归。