final transient fields serialization
David M. Lloyd
david.lloyd at redhat.com
Mon Nov 9 23:38:59 UTC 2009
On 11/09/2009 03:30 PM, Pawel Veselov wrote:
> it again caught my attention, and I though that may be there is
> something that can be done about this.
> The issue is obvious -- having 'final transient' instance fields makes
> little sense if the object is ever serialized.
> Logically, there may be perfect reasoning behind making an instance
> field final, as well as transient, in which case there is then no
> mechanism to reinitialize this field on object deserialization.
I've used final transient fields before to hold values which are not
relevant on the remote side (and thus can be null or 0), but point taken...
> It seems that it would be nice if either the final fields were
> initialized in a separate block that would be executed on
> deserialization, or if readObject() could set them. After all you can
> have a code block that sets the final fields. Not sure how feasible that
> is, but IMHO, that is a short coming.
One possible problem with this is that changing a final field might have
some JMM implications (case in point, CopyOnWriteArrayList uses
sun.misc.Unsafe#putObjectVolatile() to reinitialize the transient final
Lock field, though I don't see anywhere that ObjectInputStream itself takes
such precautions; one would think that java.lang.reflect.Field would take
care of this for you, but perhaps it does not).
A workaround for this shortcoming is as follows. Instead of using a
transient final field with a custom readObject(), use a regular final field
and add a writeObject() method which uses PutFields to change its value to
a marker object. The marker object should have a protected readResolve()
method which constructs the correct value for the field on the remote side.
Using this trick might have odd results though. For example, you can
cause two objects to have final references to each other, which is
ordinarily not possible without reflection.
Alternately, in some cases the actual new value might be computable on the
writing side. In either case there may be a bandwidth penalty however.
You could also use writeReplace() to replace the entire object with a
minimal marker, which in turn does readResolve() on the remote side to
construct the proper instance (using a constructor, thus it would be able
to initialize all fields). This approach fails in certain cyclic cases
though, because the readResolve() isn't executed until after the entire
object was deserialized, so any backreferences to the replaced object will
be filled in with the marker object until then.
More information about the core-libs-dev