2009-07-06 6 views
5

La méthode ObjectOutputStream.writeStreamHeader() peut être remplacée pour ajouter ou ajouter des données à l'en-tête. Toutefois, si ces données est basée sur un argument transmis au constructeur de la classe dérivée comme:Comment remplacer ObjectOutputStream.writeStreamHeader()?

public class MyObjectOutputStream extends ObjectOutputStream { 

    public MyObjectOutputStream(int myData, OutputStream out) throws IOException { 
     super(out); 
     m_myData = myData; 
    } 

    protected void writeStreamHeader() throws IOException { 
     write(m_myData);   // WRONG: m_myData not initialized yet 
     super.writeStreamHeader(); 
    } 

    private final int m_myData; 
} 

cela ne fonctionne pas parce que super() est appelé avant m_myData est initialisé et super() appels writeStreamHeader(). La seule façon que je peux penser à contourner c'est en utilisant ThreadLocal comme:

public class MyObjectOutputStream extends ObjectOutputStream { 

    public MyObjectOutputStream(int myData, OutputStream out) throws IOException { 
     super(thunk(myData, out)); 
    } 

    protected void writeStreamHeader() throws IOException { 
     write(m_myData.get().intValue()); 
     super.writeStreamHeader(); 
    } 

    private static OutputStream thunk(int myData, OutputStream out) { 
     m_myData.set(myData); 
     return out; 
    } 

    private static final ThreadLocal<Integer> m_myData = new ThreadLocal<Integer>(); 
} 

Cela semble fonctionner, mais est-il un moyen de mieux (moins maladroits)?

Répondre

3

Il existe un moyen général de résoudre ce genre de problème. Créez la classe et la classe interne et référencez une variable dans la portée externe.(Note, cela ne fonctionne qu'avec -target 1.4 ou Greter, qui est la valeur par défaut dans les versions actuelles de javac. Avec -target 1.3 vous obtiendrez un NPE.)

public static ObjectOutputStream newInstance(
    final int myData, final OutputStream out 
) throws IOException { 
    return new ObjectOutputStream(out) { 
     @Override 
     protected void writeStreamHeader() throws IOException { 
      write(myData); 
      super.writeStreamHeader(); 
     } 
    }; 
} 

Mais, il est probablement plus facile de écrire les données avant de construire le ObjectOuputStream.

+0

J'aime cette solution car elle écrase writeStreamHeader() "comme prévu" et l'en-tête est écrit au "bon moment" et ne fait aucune hypothèse sur le moment où la méthode est appelée par le constructeur de la classe de base. En tant que tel, il est "à l'épreuve du futur". –

+0

Neat. Apparemment, la portée externe est initialisée avant le constructeur de la superclasse. – Thilo

+0

Oui, la spécification de la VM a dû être changée pour permettre ce genre de chose. Cela peut encore se compliquer - la dernière fois que j'ai vérifié un exemple dans la JLS ne fonctionnait pas avec le javac de l'époque. –

0

Utiliser la composition au lieu de l'héritage.

public class MyObjOutStream implements DataOutput, ObjectOutput, ObjectStreamConstants { 
    //same interfaces that ObjectOutputStream implements 

    private ObjectOutputStream objOutStream; 

    //implement all the methods below 
} 

instance ObjectOutputStream que lorsque vous êtes prêt à le faire. Les méthodes restantes que vous devez implémenter dans les interfaces peuvent simplement appeler les mêmes méthodes sur objOutStream

+0

c'est encore plus maladroit que d'utiliser ThreadLocal imho – dfa

+0

Comment est-ce que c'est maladroit? La composition est très élégante. Le seul problème est tout le code standard (corps de méthodes) car Java n'a pas une syntaxe agréable pour les délégués. Mais Eclipse peut les générer automatiquement. – Thilo

+2

@Thilo, je préférerais que votre solution utilise la composition de flux – dfa

1

Il est généralement une mauvaise idée d'appeler des méthodes non finales du constructeur (pour exactement la raison que vous avez présentée).

Pouvez-vous réaliser votre sérialisation personnalisée sans étendre ObjectOutputStream? Je pense à la composition du cours d'eau. Par exemple, vous pouvez préfixer votre en-tête en l'écrivant dans le OutputStream sous-jacent avant ObjectOutputStream. Cela ne peut évidemment pas être fait dans une sous-classe de ObjectOutputStream, mais cela peut facilement être fait de l'extérieur.

out.write(myExtraHeader); 
    ObjectOutputStream oos = new ObjectOutputStream(out); 

Si vous voulez, vous pouvez envelopper tout cela gentiment derrière l'interface ObjectOutput comme Stu Thompson a suggéré dans sa réponse, afin qu'il puisse se tourner vers l'extérieur presque comme un ObjectOutputStream.

Mise à jour:

En regardant les JavaDocs et la source pour ObjectOutputStream, il y a un second constructeur (protégé), qui ne remet pas writeStreamHeader().

Cependant, ce constructeur n'initialise pas non plus d'autres structures internes. Pour citer les documents, il est prévu "pour les sous-classes qui réimplémentent complètement ObjectOutputStream de ne pas avoir à allouer des données privées juste utilisées par cette implémentation de ObjectOutputStream". Dans ce cas, il appelle également d'autres méthodes, telles que "writeObjectOverride". Messy ...

1

Vous ne pourriez pas le faire comme ça. Ignorer l'appel writeStreamHeader du constructeur super et faire un vous-même, lorsque vous avez initialisé le champ nécessaire:

public class MyObjectOutputStream extends ObjectOutputStream { 

private boolean initalized = false; 
private final int m_myData; 

protected MyObjectOutputStream(int myData, OutputStream out) throws IOException, SecurityException { 
    super(out); 
    m_myData = myData; 
    initalized = true; 
    writeStreamHeader(); 
} 

protected void writeStreamHeader() throws IOException { 

    if(!initalized){ 
     return; 
    } 

    write(m_myData); 
    super.writeStreamHeader(); 
} 
} 

EDIT:

Ou, comme l'a suggéré Thilo, il pourrait être écrit comme:

public class MyObjectOutputStream extends ObjectOutputStream { 

    private final int m_myData; 

    protected MyObjectOutputStream(int myData, OutputStream out) throws IOException, SecurityException { 
     super(out); 
     m_myData = myData; 
     write(m_myData); 
     super.writeStreamHeader(); 
    } 

    protected void writeStreamHeader() throws IOException { 
     // work is done in the constructor 
    } 
} 
+0

Cela fonctionnerait, je suppose ... – Thilo

+1

Dans le même ordre d'idée, peut-être plus clair/plus propre: Surchargez writeStreamHeader pour ne rien faire, et appelez super.writeStreamHeader() depuis votre constructeur. De cette façon, il n'y a pas besoin du drapeau init. – Thilo

+0

Excellente idée, vient d'ajouter un exemple. –

Questions connexes