2009-07-06 13 views
5

El método ObjectOutputStream.writeStreamHeader() se puede anular para anteponer o anexar datos al encabezado. Sin embargo, si esos datos se basa en un argumento pasado al constructor de la clase derivada como:Cómo anular 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; 
} 

que no funciona porque super() se llama antes de m_myData se inicializa y super() llamadas writeStreamHeader(). La única manera que puedo pensar para evitar esto es mediante el uso de ThreadLocal como:

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

Esto parece funcionar, pero hay una manera mejor (menos torpe)?

Respuesta

3

Hay una manera general de resolver este tipo de problema. Haz la clase y la clase interna y haz referencia a una variable en el ámbito externo.(Nota: esto sólo funciona con -target 1.4 o Greter, que es el valor por defecto en las versiones actuales de javac. Con -target 1.3 obtendrá una 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(); 
     } 
    }; 
} 

embargo, es probable que sea más fácil sólo para escribir los datos a cabo antes de construir el ObjectOuputStream.

+0

Me gusta esta solución porque anula writeStreamHeader() "como estaba previsto" y el encabezado se escribe en el "momento correcto" y no hace suposiciones sobre cuándo el constructor de la clase base llama al método. Como tal, es "a prueba de futuro". –

+0

aseado. Aparentemente, el alcance externo se inicializa antes que el constructor de la superclase. – Thilo

+0

Sí, la especificación de VM tuvo que cambiarse para permitir ese tipo de cosas. Todavía puede complicarse, la última vez que verifiqué que un ejemplo en el JLS no funcionaba con el javac de la época. –

0

Usa la composición en lugar de la herencia.

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

    private ObjectOutputStream objOutStream; 

    //implement all the methods below 
} 

Instale el ObjectOutputStream solo cuando esté listo para hacerlo. Los métodos restantes que necesita implementar en las interfaces pueden simplemente llamar a los mismos métodos en objOutStream

+0

esto es aún más torpe que usar ThreadLocal imho – dfa

+0

¿Cómo es eso torpe? La composición es muy elegante. El único problema es todo el código repetitivo (cuerpos de método) porque Java carece de una buena sintaxis para los delegados. Pero Eclipse puede autogenerarlos. – Thilo

+2

@Thilo, preferiría su solución usando la composición de flujo – dfa

1

En general, es una mala idea llamar a métodos no finales desde el constructor (exactamente por la razón que presentó).

¿Se puede lograr su serialización personalizada sin extender ObjectOutputStream? Estoy pensando en la composición de la secuencia. Por ejemplo, podría anteponer su encabezado escribiéndolo al OutputStream subyacente antes de que ObjectOutputStream lo haga. Obviamente, esto no se puede hacer en una subclase de ObjectOutputStream, pero se puede hacer fácilmente desde el exterior.

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

Si lo desea, puede envolver todo esto muy bien detrás de la interfaz ObjectOutput como Stu Thompson sugirió en su respuesta, de modo que pueda mirar al exterior casi como un ObjectOutputStream.

Actualización:

En cuanto a los JavaDocs y fuente de ObjectOutputStream, hay un segundo constructor (protegida), que no llama writeStreamHeader().

Sin embargo, este constructor tampoco inicializa otras estructuras internas. Para citar los documentos, está destinado "para las subclases que están reimplantando por completo ObjectOutputStream para no tener que asignar los datos privados que acaba de utilizar esta implementación de ObjectOutputStream". En este caso, también llama a otros métodos, como "writeObjectOverride". Messy ...

1

¿No podría hacerlo así? Ignorar la llamada writeStreamHeader desde el constructor super y hacer uno usted mismo, cuando se ha inicializado el campo necesario:

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:

O, como sugiere Thilo, podría escribirse así:

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

Eso funcionaría, supongo ... – Thilo

+1

En la misma línea, quizás más claro/más limpio: anule writeStreamHeader para no hacer nada, y llame a super.writeStreamHeader() desde tu constructor De esta manera, no hay necesidad de la bandera de inicio. – Thilo

+0

Gran idea, acabo de agregar un ejemplo. –