2009-07-06 12 views
5

Metoda ObjectOutputStream.writeStreamHeader() może zostać nadpisana, aby przedłużyć lub dołączyć dane do nagłówka. Jednak, jeśli dane są oparte na argument przekazany do konstruktora klasy pochodnej, niczym:Jak przesłonić 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; 
} 

to nie działa, ponieważ super() jest wywoływana przed m_myData jest inicjowany i super() rozmowy writeStreamHeader(). Jedynym sposobem mogę myśleć, aby obejść ten jest za pomocą ThreadLocal jak:

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

To wydaje się działać, ale jest tam lepsze (mniej niezgrabne) sposób?

Odpowiedz

3

Istnieje ogólny sposób rozwiązania tego problemu. Utwórz klasę i klasę wewnętrzną i podaj zmienną w zewnętrznym zasięgu.(Uwaga, to działa tylko z -target 1.4 lub greter, który jest domyślny w aktualnych wersjach javac. Z -target 1.3 dostaniesz 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(); 
     } 
    }; 
} 

Ale to chyba łatwiej po prostu zapisywać dane przed konstruowania ObjectOuputStream.

+0

Podoba mi się to rozwiązanie, ponieważ przesłania on writeStreamHeader() "zgodnie z przeznaczeniem", a nagłówek jest zapisywany w "właściwym czasie" i nie zawiera żadnych założeń dotyczących tego, kiedy metoda jest wywoływana przez konstruktor klasy podstawowej. Jako taki jest "odporny na przyszłość". –

+0

Schludny. Podobno zewnętrzny zakres zostanie zainicjowany przed konstruktorem superklasy. – Thilo

+0

Tak, specyfikacja VM musiała zostać zmieniona, aby umożliwić tego rodzaju rzeczy. Nadal może się skomplikować - ostatni raz sprawdziłem przykład w JLS nie działał z javakiem czasu. –

0

Użyj składu zamiast dziedziczenia.

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

    private ObjectOutputStream objOutStream; 

    //implement all the methods below 
} 

Wystąp obiekt ObjectoutputStream tylko wtedy, gdy jesteś gotowy. Pozostałe metody, które musisz wdrożyć w interfejsie, mogą wywoływać te same metody na objOutStream

+0

jest to jeszcze bardziej niezgrabne niż używanie ThreadLocal imho – dfa

+0

Jak to jest niezgrabne? Kompozycja jest bardzo elegancka. Jedynym problemem jest cały kod tablicy (ciała metod), ponieważ Java nie ma dobrej składni dla delegatów. Ale Eclipse może je automatycznie generować. – Thilo

+2

@Thilo, Wolałbym twoje rozwiązanie przy użyciu strumienia kompozycji – dfa

1

Generalnie nie jest dobrym pomysłem wywoływanie metod niekońcowych z konstruktora (z dokładnie tego powodu, który przedstawiłeś).

Czy możesz osiągnąć własną serializację bez rozszerzania ObjectOutputStream? Myślę o składzie strumienia. Na przykład możesz przedłużyć swój nagłówek, pisząc go do podstawowego strumienia wyjściowego, zanim zrobi to ObjectOutputStream. Oczywiście nie można tego zrobić w podklasie ObjectOutputStream, ale można to łatwo zrobić z zewnątrz.

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

Jeśli chcesz, możesz zawinąć to wszystko się ładnie za interfejs ObjectOutput jako Stu Thompson zasugerował w swojej odpowiedzi, tak aby mogła ona wyglądać na zewnątrz prawie jak ObjectOutputStream.

Aktualizacja:

Patrząc na Javadocs i źródło dla ObjectOutputStream, istnieje druga (chronione) konstruktor, który nie wymaga writeStreamHeader().

Jednak ten konstruktor również nie inicjuje innych struktur wewnętrznych. Aby zacytować dokument, jest przeznaczony "dla podklasów, które całkowicie reimplementują ObjectOutputStream , aby nie musieć przydzielać prywatnych danych używanych tylko przez tę implementację ObjectOutputStream". W tym przypadku wywołuje również inne metody, takie jak "writeObjectOverride". Brudny ...

1

Nie możesz tego zrobić tak. Zignorować połączenie writeStreamHeader z super konstruktora i zrobić samemu, podczas inicjalizacji potrzebne murawę:

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:

Albo, jak sugeruje Thilo, to można zapisać następująco:

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

To by działało, tak przypuszczam ... – Thilo

+1

W tym samym wierszu, być może jaśniejsze/czystsze: Zastąpić writeStreamHeader, aby nic nie robić, i wywołać super.writeStreamHeader() z twój konstruktor. W ten sposób flaga init nie jest potrzebna. – Thilo

+0

Świetny pomysł, właśnie dodałem przykład. –