4

Let's say I am defining a custom writeObject and readObject for my class for serialization purpose. The class has a final attribute(int) which is initialized in constructor. During writeObject there are no issues. But while reading the object back, I cannot assign the value to the attribute as compiler complains that I can't override the final attribute and asks me to remove the final modifier from the attribute. Is there a way out for this?

Below class might give you clear picture of what I am trying to achieve. this.age = in.readInt(); in readObject() gives me compilation error.

public class Person {

private String name = null;
private final int age;

public Person(String name, int age)
{
    this.name = name;
    this.age = age;
}

public void writeObject(ObjectOutputStream out) throws IOException
{
    out.writeObject(name);
    out.writeInt(age);
}

public void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
    this.name = (String) in.readObject();
    this.age = in.readInt();
}

}
Archmede
  • 1,592
  • 2
  • 20
  • 37
Simpson
  • 292
  • 3
  • 13
  • 1
    try [readReplace()](http://stackoverflow.com/questions/1168348/java-serialization-readobject-vs-readresolve) – Katona Aug 30 '13 at 17:40
  • 1
    Why? You don't need these methods at all. Just use the default behaviour. Or, call defaultWriteObject() and defaultReadObject() inside them as intended by the designers. Either way, all your fields get serialized automatically, including finals. – user207421 Aug 31 '13 at 00:37
  • The above code snippet is just a example. Strictly speaking, I have to define my custom serialization methods as the class contains complex attributes. – Simpson Sep 04 '13 at 01:45
  • @Katona there is no `readReplace`, there is a `readResolve` – Grim Feb 29 '16 at 16:51
  • Nevertheless you can use default serialization via `defaultWriteObject()/defaultReadObject()` for the non-complex attributes such as your `final` field, and mark the 'complex' ones as `transient` so you can handle them yourself, or omit them from the `serialFields` member if you supply that. There is no need for any more complicated solution. – user207421 Jun 04 '21 at 01:31

3 Answers3

2

The default ObjectInputStream deserialization seems to use sun.misc.Unsafe to set fields (java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(Object, Object[])) so setting a final field is probably not something you want to be doing. As Katona suggested in the comments you can instead do something like :

public class Person implements Serializable {

    private String name = null;

    private final int age;

    private int ageFromRead;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws IOException,
    ClassNotFoundException {
        this.name = (String) in.readObject();
        this.ageFromRead = in.readInt();
    }

    private Object readResolve() {
        return new Person(name, ageFromRead);
    }
}
SamYonnou
  • 2,068
  • 1
  • 19
  • 23
  • Thanks @user1571871. But what if I have many final attributes, should I go about creating dummy attribute for everything ! Seriously? – Simpson Aug 30 '13 at 20:56
  • Seems that way. Unless you can get away with just using the default serialization (not implementing read/write) which will do all of the final setting for you. – SamYonnou Sep 03 '13 at 12:35
  • Yep, I forgot to update here. I did go with a mix of default serialization followed by my custom definition :) – Simpson Sep 04 '13 at 01:42
1

The problem with readResolve is that you need to temporarily have all the state in the instance that will be replaced by readResolve. This doesn't blend well with final.

Scenario: you want to pull something that's full of undesired mutability into the modern age. But without losing compatibility of the serialized form. And preferrably without growing mad bucket-brigading temporary state from readObject to readResolve. Preferrably without writing a full-fledged SerializationProxy that might be ruled out by compatibility requirements anyways.

Solution: bundle that temporary state up in a single Supplier closure:

public class AncientSerializableExample implements Serializable {
    // we have two fields in the example to illustrate the 
    // transient, because we don't want serializability defaults to interfere
    final public transient ImmutableList<Integer> immutableInts;
    final public transient ImmutableList<String> immutableStrings;

    /** funnels the data from readObject to readResolve, transient for obvious reasons,
     * we keep all the mutability reaquired to pass data to readResolve contained in here */
    private transient Supplier<AncientSerializableExample> deserializationResolver;

    public AncientSerializableExample(
            List<Integer> ints,
            List<String> strings
    ) {
        this.immutableInts = ImmutableList.copyOf(ints);
        this.immutableStrings = ImmutableList.copyOf(strings);
    }
    private void writeObject(final ObjectOutputStream out) throws IOException {
        // that ancient serializable form we want to keep untouched clearly wasn't using guava
        out.writeObject(new ArrayList<>(immutableInts));
        out.writeObject(new ArrayList<>(immutableStrings));
    }
    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        List<Integer> freshlyReadInts = (List<Integer>) in.readObject();
        List<String> freshlyReadStrings = (List<String>) in.readObject();

        deserializationResolver = () -> { // our Supplier<AncientSerializableExample> captures the temporary state so conveniently!
            deserializationResolver = null; // don't keep the closure, it would prevent the deserialized ArrayLists from getting GCed
            return new AncientSerializableExample(
                    freshlyReadInts,
                    freshlyReadStrings
            );
        };
    }
    /** readResolve won't get any more complicated than this no matter how many fields reality throws at our class,
     * and the constructor call is nicely paired with the ObjectInputStream reading and no amount of formatting anarchy
     * thrown at the class can change that */
    private Object readResolve() throws ObjectStreamException {
        return deserializationResolver.get();
    }
}
usrusr
  • 39
  • 1
0

I found a great example here which uses reflection to set the final variable.

I'll try to convert it to a simple example:

public class SerializableClass implements Serializable {

    private final String finalVariable;

    /* Constructor and other methods */

    private void readObject(ObjectInputStream iStream) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField fields = iStream.readFields();


    try {
        Field id = this.getClass().getDeclaredField("finalVariable");

        // make finalVariable non "final"
        id.setAccessible(true);
        id.set(this, fields.get("finalVariable", null));

        // make field final again
        id.setAccessible(false);
    }
    catch (IllegalAccessException | NoSuchFieldException e) {
        System.out.println(e.getClass() + " : " + e.getMessage());
    }
}
Archmede
  • 1,592
  • 2
  • 20
  • 37