【问题标题】:Why does PrintStream.close() end up getting called twice?为什么 PrintStream.close() 最终会被调用两次?
【发布时间】:2009-05-12 13:36:13
【问题描述】:

令我惊讶的是,以下代码打印出两次“关闭”。通过调试器运行,似乎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 吗?

【问题讨论】:

  • 这是一个调试器的优点。在 close 方法中放置一个断点,您应该可以看到它被调用的原因。

标签: java inheritance printstream


【解决方案1】:

如果您在调试器中查看代码并在 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 

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

顺便说一下,如果您之所以问这个问题是因为您注意到 close() 方法中的自定义代码运行了两次,那么您真的应该检查一下 this.closingPrintStream.close() 将此设置为 true(以及类的 cmets 状态 /* To avoid recursive closing */)。

【讨论】:

  • +1 用于教育用户将来如何自己解决这个问题
  • “关闭”实例变量是 PrintStream 私有的,所以我不能检查它,当然我可以使用自己的。
  • jdk 中有几个类做类似的事情。我相信这是因为在某些情况下 A 类和 B 类相互引用,并且用户可能对 A 或 B 有引用,关闭其中任何一个应该关闭另一个。如前所述,您通常应该保护您的 close 方法免受多次调用(尽管递归调用是一种更隐蔽且更少预期的情况)。
  • 很好的答案。 simonn - 我认为建议是在需要时添加您自己的关闭实例变量。
【解决方案2】:

看看 PrintStream 的源代码。

它有两个对底层 Writer textOutcharOut 的引用,一个是基于字符的,一个是基于文本的(不管是什么意思)。此外,它还继承了对基于字节的 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;

close() 方法中,它会关闭所有这些(textOutcharOut 基本相同)。

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

现在,有趣的部分是 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));
}

所以,对close() 的调用将调用charOut.close(),而charOut.close() 又会再次调用原来的close(),这就是我们使用关闭标志来缩短无限递归的原因。

【讨论】:

    猜你喜欢
    • 2021-07-13
    • 2021-11-25
    • 2015-12-24
    • 2012-12-10
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多