2009-05-12 15 views
7

Para mi sorpresa, el siguiente código imprime "Cerrar" dos veces. Al ejecutar el depurador, parece que MyPrintStream.close() llama al super.close(), que termina llamando al MyPrintStream.close() nuevamente.¿Por qué PrintStream.close() termina siendo llamado dos veces?

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

¿Por qué sucede esto? ¿No debería extender PrintStream?

+0

Algo para lo que un depurador es bueno. Coloque un punto de interrupción en el método de cierre y debería poder ver por qué se llama. –

Respuesta

1

Eche un vistazo a la fuente de PrintStream.

Tiene dos referencias al escritor subyacente textOut y charOut, una base de caracteres y una basada en texto (lo que sea que eso signifique). Además, hereda una tercera referencia al OutputStream basado en bytes, llamado 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; 

En el método close() cierra todos ellos (textOut es básicamente el mismo que 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; 
    } 
} 
} 

Ahora, la parte interesante es que charOut contiene un (envuelto) hace referencia a la PrintStream sí (nótese el init(new OutputStreamWriter(this)) en el 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)); 
} 

Por lo tanto, la llamada a close() llamarán charOut.close(), que a su vez vuelve a llamar al close() original, por lo que tenemos el indicador de cierre para acortar la recursión infinita.

11

Si nos fijamos en el código en un depurador y establecer un punto de interrupción en el método close(), se va a revelar los stacktraces de que está llamando a su método de close():

  1. su principal método
  2. línea() sun.nio.cs.StreamEncoder $ CharsetSE.implClose 431

la StackTrace completa para estos últimos se ve así:

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 

Por desgracia, aunque no puedo decir por qué StreamEncoder llamaría de nuevo en su PrintStream embargo, como mi IDE no tiene un accesorio de fuente para sun.nio.cs.StreamEncoder :(Esto es cierto JDK 6 si eso importa

Por cierto, si usted está haciendo esta pregunta porque se dieron cuenta de que el código personalizado en su método de close() se ejecuta dos veces, que realmente debería estar mirando si this.closing. PrintStream.close() establece esto en verdadero (y los comentarios de la clase indican /* To avoid recursive closing */).

+1

+1 para educar al usuario sobre cómo resolver esto por sí mismo en el futuro –

+1

La variable de instancia 'de cierre' es privada para PrintStream, así que no puedo verificarla, aunque por supuesto puedo usar la mía. –

+2

Hay un par de clases en el jdk que hacen algo similar.Creo que es porque hay situaciones en las que las clases A y B se refieren entre sí, y el usuario puede tener una referencia a A o B, y cerrar cualquiera de ellas debería cerrar la otra. Como se mencionó, generalmente debería proteger sus métodos cercanos de múltiples invocaciones (aunque la invocación recursiva es una situación más insidiosa y menos esperada). – james

Cuestiones relacionadas