简体   繁体   中英

How to properly use transient keyword when de-serializing?

I have the following class

public class Num implements Serializable, Observable {
    private int num; // number which should be serialized

    // listeners who are watching for changes in this Num
    // No need to serialize these -- pointless to do so.
    private transient Set<Observer> listeners = new HashSet<>();

    @Override public void addListener(Observer o) { listeners.add(o); }

    @Override public void removeListener(Observer o) { listeners.remove(o); }

    public void set(int newVal) {
        if (num != newVal) {
            num = newVal; // set the value.
            for (Observer o : listeners)
                o.notify(); // Notify listeners that the value changed.
        }
    }
}

When I serialize this class, it works well and num is saved. When I de-serialize the class, num is loaded but listeners is not and is set to null . My program then crashes on the line for (Observer o: listeners) .

I came up with some solutions but they are all terrible solutions.

1) Have a setup method which 'reconstructs' the transient fields.

public void setup() {
    if (listeners != null) throw new Exception("Already setup!");
    listeners = new HashSet<>();
}

This way is annoying through because the de-serialization method needs to remember to setup the object. It's very unintuitive for other people working on the project.

2) Have the set method automatically check and repair itself

public void set(int newVal) {
    if (num != newVal) {
        if (listeners == null) listeners = new HashSet<>();
        ...
    }
}

This way is also bad because the check will keep happening over and over, even though it only needed to be done once.

3) Remove Observable from class Num , get rid of listeners , etc. Then make a non-serializable class which contains a Num instance.

public class Num implements Serializable {
    public int num;
}

public class ObservableNum impements Observable {
    private Num n;

    public ObservableNum() { n = new Num(); } // constructor
    private ObservableNum(Num n) { this.n = n; } // constructor

    ...

    public static ObservableNum loadNum(...) {

        ObjectInputStream ois = ...;
        return new ObservableNum((Num) ois.readObject());
    }
}

But this way also seems needlessly complicated. Surely there must be a better solution? How is transient properly used?

From documentation :

There is, however, a strange yet crafty solution. By using a built-in feature of the serialization mechanism, developers can enhance the normal process by providing two methods inside their class files.

Those methods are:

  1. private void writeObject(ObjectOutputStream out) throws IOException;
  2. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

So, you just need to implement readObject which looks like below and after default deserialisation just create new instance of HashSet . Example:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    this.listeners = new HashSet<>();
}

Method 2 is a good approach. In my practice I will always check for null when using for loop or stream / foreach with collection.

An optimization can be done by initializing the set in addListener method so you can directly return in set method if the collection is empty.

For your performance concerning, a null-check statement will not affect the performance as the for-loop and notify method will cost more time and it can be ignored.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM