2009-05-12 8 views
7

Un peu à ma grande surprise, le code suivant affiche deux fois "Fermer". En cours d'exécution à travers le débogueur, il semble MyPrintStream.close() appels super.close(), qui finit par appeler à nouveau MyPrintStream.close().Pourquoi PrintStream.close() finit par être appelé deux fois?

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

Pourquoi cela se produit-il? Ne devrais-je pas étendre PrintStream?

+0

C'est quelque chose qu'un débogueur est bon pour. Placez un point d'arrêt dans la méthode close et vous devriez être en mesure de voir pourquoi il est appelé. –

Répondre

1

Jetez un oeil à la source de PrintStream.

Il possède deux références à l'enregistreur sous-jacent textOut et charOut, une base de caractères et une base de texte (quel que soit ce que cela signifie). En outre, il hérite d'une troisième référence au OutputStream basé sur un octet, appelé 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; 

Dans la méthode close() il ferme tous (textOut est essentiellement la même que celle 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; 
    } 
} 
} 

Maintenant, la partie intéressante est que charOut contient un (emballé) fait référence à la PrintStream lui-même (notez le init(new OutputStreamWriter(this)) dans le constructeur)

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

Ainsi, l'appel à close() appellera charOut.close(), qui à son tour appelle l'original close() à nouveau, ce qui explique pourquoi nous avons le drapeau de fermeture pour couper court à la récursion infinie.

11

Si vous regardez votre code dans un débogueur et de définir un point d'arrêt dans la méthode close(), il va révéler les stacktraces de qui appelle votre méthode close():

  1. votre principale méthode
  2. sun.nio.cs.StreamEncoder ligne CharsetSE.implClose() $ 431

le stacktrace complet pour ce dernier ressemble à:

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 

Malheureusement si je ne peux pas dire pourquoi StreamEncoder rappellerait dans votre PrintStream bien, comme mon IDE ne dispose pas d'une pièce jointe de source pour sun.nio.cs.StreamEncoder :(C'est JDK 6 BTW , si ça compte.

Par ailleurs, si vous posez cette question parce que vous remarqué que le code personnalisé dans votre méthode close() est en cours d'exécution deux fois, vous devriez vraiment vérifier si this.closing. PrintStream.close() définit true (les commentaires de la classe indiquent /* To avoid recursive closing */).

+1

+1 pour éduquer l'utilisateur comment résoudre lui-même le problème dans le futur –

+1

La variable d'instance 'closing' est privée à PrintStream, donc je ne peux pas la vérifier, bien que je puisse bien sûr utiliser la mienne. –

+2

Il y a quelques classes dans le jdk qui font quelque chose de similaire.Je crois que c'est parce qu'il y a des situations où les classes A et B se réfèrent l'une à l'autre, et l'utilisateur peut avoir une référence à A ou à B, et fermer l'un d'entre eux devrait fermer l'autre. Comme mentionné précédemment, vous devriez généralement protéger vos méthodes de fermeture de plusieurs appels (bien que l'invocation récursive soit une situation plus insidieuse et moins attendue). – james

Questions connexes