2009-07-06 9 views
5

È possibile sovrascrivere il metodo ObjectOutputStream.writeStreamHeader() per aggiungere o aggiungere dati all'intestazione. Tuttavia, se i dati si basa su un argomento passato al costruttore della classe derivata come:Come eseguire l'override di 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; 
} 

non funziona perché super() viene chiamato prima m_myData è inizializzato e super() chiamate writeStreamHeader(). L'unico modo che posso pensare per ovviare a questo è quello di utilizzare ThreadLocal come:

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

Questo sembra funzionare, ma c'è un modo migliore (meno goffo)?

risposta

3

Esiste un modo generale per risolvere questo tipo di problema. Crea la classe e la classe interiore e fai riferimento a una variabile nell'ambito esterno.(Nota, questo funziona solo con -target 1.4 o greter, che è l'impostazione predefinita nelle versioni correnti di javac. Con -target 1.3 si otterrà 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(); 
     } 
    }; 
} 

Ma, è probabilmente più facile solo per scrivere i dati prima di costruire il ObjectOuputStream.

+0

Mi piace questa soluzione perché sovrascrive writeStreamHeader() "come previsto" e l'intestazione viene scritta nel "momento giusto" e non fa ipotesi su quando il metodo viene chiamato dal costruttore della classe base. In quanto tale, è "a prova di futuro". –

+0

Neat. Apparentemente l'ambito esterno viene inizializzato prima del costruttore della superclasse. – Thilo

+0

Sì, le specifiche VM dovevano essere cambiate per consentire quel genere di cose. Può ancora diventare complicato - l'ultima volta che ho controllato un esempio nel JLS non funzionava con il javac del tempo. –

0

Utilizzare la composizione anziché l'ereditarietà.

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

    private ObjectOutputStream objOutStream; 

    //implement all the methods below 
} 

Installa ObjectOutputStream solo quando sei pronto a farlo. I restanti metodi che devi implementare nelle interfacce possono semplicemente chiamare gli stessi metodi su objOutStream

+0

questo è ancora più goffo rispetto all'utilizzo di ThreadLocal imho – dfa

+0

Come è quel clunky? La composizione è molto elegante. L'unico problema è tutto il codice boilerplate (corpi dei metodi) perché a Java manca una bella sintassi per i delegati. Ma Eclipse può auto-generarli. – Thilo

+2

@Thilo, preferirei la tua soluzione utilizzando la composizione dello stream – dfa

1

In genere è una cattiva idea chiamare i metodi non definitivi dal costruttore (esattamente per la ragione che hai presentato).

È possibile ottenere la serializzazione personalizzata senza estendere ObjectOutputStream? Sto pensando alla composizione del flusso. Ad esempio, puoi anteporre la tua intestazione scrivendola all'OutputStream sottostante prima di ObjectOutputStream. Questo ovviamente non può essere fatto in una sottoclasse di ObjectOutputStream, ma può essere fatto facilmente dall'esterno.

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

Se si vuole, si può avvolgere tutto questo ben disposti dietro l'interfaccia ObjectOutput come Stu Thompson ha suggerito nella sua risposta, in modo che possa guardare al di fuori quasi come un ObjectOutputStream.

Aggiornamento:

Guardando le JavaDocs e fonte ObjectOutputStream, v'è un secondo (protetto) costruttore, che non chiama writeStreamHeader().

Tuttavia, questo costruttore non inizializza anche altre strutture interne. Per citare i documenti, è inteso "per sottoclassi che stanno completamente reimplementando ObjectOutputStream per non dover allocare dati privati ​​utilizzati da questa implementazione di ObjectOutputStream". In questo caso chiama anche altri metodi, come "writeObjectOverride". Disordinato ...

1

Non potresti farlo in questo modo. Ignorare la chiamata writeStreamHeader dal costruttore super e fare uno voi stessi, dopo aver inizializzato il campo necessaria:

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:

Oppure, come suggerito da Thilo, potrebbe essere scritta come:

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

Che funzionerebbe, suppongo ... – Thilo

+1

Lungo le stesse linee, forse più chiaro/pulito: Ignora writeStreamHeader per non fare nulla e chiama super.writeStreamHeader() da il tuo costruttore. In questo modo, non è necessario il flag di init. – Thilo

+0

Ottima idea, ho appena aggiunto un esempio. –