Почему именно PrintStream.close() в конечном итоге получить вызов дважды?
К моему удивлению, следующий код выводит" закрыть " дважды. Запуск через отладчик, кажется, 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?
2 ответа:
Взгляните на источник PrintStream.
Он имеет две ссылки на лежащего в основе автора
textOut
иcharOut
, одну символьную базу и одну текстовую (что бы это ни значило). Кроме того, он наследует третью ссылку на байтовый выходной поток, называемый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()
он закрывает все из них (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; } } }
Теперь интересная часть состоит в том, что 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()
, который, в свою очередь, снова вызовет исходныйclose()
, поэтому у нас есть флаг закрытия, чтобы прервать бесконечную рекурсию.
Если вы посмотрите на свой код в отладчике и установите точку останова в методе
close()
, он покажет stacktracks , который вызывает ваш методclose()
:
- ваш основной метод
- солнце.НИО.CS.StreamEncoder$CharsetSE.implClose () строка 431
Полный stacktrace для последнего выглядит следующим образом:
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 не имеет источника привязанность к Солнцу.НИО.CS.StreamEncoder : (это JDK 6 кстати, если это имеет значение.
Кстати , если вы задаете этот вопрос, потому что заметили, что пользовательский код в вашем методе
close()
выполняется дважды, вы действительно должны проверить, еслиthis.closing
.PrintStream.close()
устанавливает это значение в true (и состояние комментариев класса/* To avoid recursive closing */
).