views:

315

answers:

4

The method ObjectOutputStream.writeStreamHeader() can be overridden to prepend or append data to the header. However, if that data is based on an argument passed to the derived class's constructor like:

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

it doesn't work because super() is called before m_myData is initialized and super() calls writeStreamHeader(). The only way I can think to work around this is by using ThreadLocal like:

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

This seems to work, but is there a better (less clunky) way?

A: 

Use composition instead of inheritance.

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

    private ObjectOutputStream objOutStream;

    //implement all the methods below
}

Instance the ObjectOutputStream only when you are ready to do so. The remaining methods you need to implement in the interfaces can just call the same methods on objOutStream

Stu Thompson
this is even more clunky than using ThreadLocal imho
dfa
How is that clunky? Composition is very elegant. The only problem is all the boilerplate code (method bodies) because Java lacks a nice syntax for delegates. But Eclipse can auto-generate them.
Thilo
@Thilo, I would prefer your solution using stream composition
dfa
@dfa: In effect, this is what is happening here, too. The only reason I did not write "object composition" is that I did not know that there is the ObjectOutput interface. But you are right, if he does not need to pass the stream around, he does not need to make it look like an ObjectOutputStream. But then he would not have needed to sub-class in the first place.
Thilo
+1 @Thilo,dfa: I agree with them, as a good design practice separate things which are bound to change in a separate class. This can be easily achieved by composition.
Gaurav Saini
@Paul: Advise: Look at the Strategy and Decorator patterns from Gof
Gaurav Saini
+1  A: 

It is generally a bad idea to call non-final methods from the constructor (for exactly the reason you presented).

Can you achieve your custom serialization without extending ObjectOutputStream? I am thinking about stream composition. For example, you could prepend your header by writing it to the underlying OutputStream before ObjectOutputStream does. This can obviously not be done in a subclass of ObjectOutputStream, but it can easily be done from the outside.

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

If you want, you can wrap this all up nicely behind the ObjectOutput interface as Stu Thompson suggested in his answer, so that it can look to the outside almost like an ObjectOutputStream.

Update:

Looking at the JavaDocs and source for ObjectOutputStream, there is a second (protected) constructor, that does not call writeStreamHeader().

However, this constructor also does not initialize other internal structures. To quote the docs, it is intended "for subclasses that are completely reimplementing ObjectOutputStream to not have to allocate private data just used by this implementation of ObjectOutputStream". In this case it also calls some other methods, such as "writeObjectOverride". Messy...

Thilo
+1  A: 

Couldn't you do it like this. Ignore the writeStreamHeader call from the super constructor and do one yourself, when you have initialized the needed field:

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:

Or, as suggested by Thilo, it could be written like:

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
    }
}
Tim Büthe
That would work, I suppose...
Thilo
Along the same lines, maybe clearer/cleaner: Override writeStreamHeader to do nothing, and call super.writeStreamHeader() from your constructor. This way, there is no need for the init flag.
Thilo
Great idea, just added an example.
Tim Büthe
+2  A: 

There is a general way to solve this sort of problem. Make the class and inner class and reference a variable in the outer scope. (Note, this only works with -target 1.4 or greter, which is the default in current versions of javac. With -target 1.3 you will get an 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();
        }
    };
}

But, it's probably easier just to write the data out before constructing the ObjectOuputStream.

Tom Hawtin - tackline
I like this solution because it overrides writeStreamHeader() "as intended" and the header gets written at the "right time" and makes no assumptions about when the method is called by the base-class constructor. As such, it's "future-proof."
Paul J. Lucas
Neat. Apparently the outer scope get initialized before the superclass constructor.
Thilo
Yeah, the VM spec had to be changed to allow that sort of thing. It can still get complicated - last time I checked an example in the JLS didn't work with the javac of the time.
Tom Hawtin - tackline