2009-05-12 11 views
7

Ku mojemu zdziwieniu poniższy kod wypisuje dwa razy "Zamknij". Po uruchomieniu debugera wydaje się, że MyPrintStream.close() dzwoni super.close(), co kończy się ponownie wywoływaniem MyPrintStream.close().Dlaczego funkcja PrintStream.close() kończy się wywoływaniem dwa razy?

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

Dlaczego tak się dzieje? Czy nie powinienem rozszerzać PrintStream?

+0

To coś, do czego służy debugger. Umieść punkt przerwania w metodzie zamykania i powinieneś być w stanie zobaczyć, dlaczego jest wywoływany. –

Odpowiedz

1

Zobacz źródło PrintStream.

Ma dwa odniesienia do podstawowej wersji Writer textOut i charOut, jednej bazy znaków i jednego tekstu (cokolwiek to znaczy). Ponadto dziedziczy on trzecie odwołanie do strumienia wyjściowego opartego na bajtach, o nazwie 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; 

W sposobie close() zamyka wszystkie z nich (textOut jest w zasadzie taka sama, jak 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; 
    } 
} 
} 

Teraz, ciekawe jest to, że charOut zawiera (zawinięty) odwołuje się do samego PrintStream (uwaga init(new OutputStreamWriter(this)) w konstruktorze)

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

Więc wywołanie close() wezwie charOut.close(), co z kolei ponownie wywołuje oryginalny close(), dlatego mamy flagę zamykającą, aby uciąć nieskończoną rekursję.

11

Jeśli spojrzeć na kodzie w debugera i ustawić punkt przerwania w metodzie close(), będzie ona ujawnić stacktraces z którzy woła metodę close():

  1. swoją główną metodą
  2. sun.nio.cs.StreamEncoder $ CharsetSE.implClose() linia 431

pełna StackTrace dla późniejszych wygląda następująco:

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 

Niestety chociaż nie mogę powiedzieć dlaczego StreamEncoder nazwałbym powrotem do PrintStream choć, jak moja IDE nie ma przywiązania źródłowy sun.nio.cs.StreamEncoder :(To JDK 6 btw , jeśli to ma znaczenie.

Nawiasem mówiąc, jeśli są z tym pytaniem, ponieważ zauważyliśmy, że niestandardowy kod w metodzie close() pracuje dwa razy, to naprawdę powinno być sprawdzenie czy this.closing. PrintStream.close() ustawia tę wartość na true (i stan komentarzy klasy /* To avoid recursive closing */).

+1

+1 za pouczenie użytkownika, jak rozwiązać to samodzielnie w przyszłości –

+1

Zmienna "zamykająca" instancji jest prywatna dla PrintStream, więc nie mogę jej sprawdzić, chociaż oczywiście mogę użyć własnego. –

+2

Istnieje kilka klas w jdk, które robią coś podobnego.Uważam, że dzieje się tak dlatego, że istnieją sytuacje, w których klasy A i B odnoszą się do siebie nawzajem, a użytkownik może mieć odniesienie do A lub B, a zamknięcie któregokolwiek z nich powinno zamknąć drugie. Jak wspomniano, powinieneś ogólnie chronić swoje zamknięte metody przed wielokrotnymi wywołaniami (chociaż rekurencyjne wywołanie jest bardziej podstępną i mniej oczekiwaną sytuacją). – james

Powiązane problemy