Как правильно использовать временные ключевые слова при десериализации? - PullRequest
2 голосов
/ 30 октября 2019

У меня есть следующий класс

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

Когда я сериализую этот класс, он работает хорошо и num сохраняется. При десериализации класса num загружается, а listeners - нет и устанавливается на null. Затем моя программа падает на линии for (Observer o : listeners).

Я нашел несколько решений, но все они ужасные решения.

1) Есть метод настройки, который 'реконструирует 'transient поля.

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

Этот способ раздражает, потому что метод десериализации должен помнить, чтобы установить объект. Это очень не интуитивно понятно для других людей, работающих над проектом.

2) Имейте метод set, чтобы автоматически проверять и восстанавливать себя

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

Этот способ также плохпотому что проверка будет повторяться снова и снова, даже если ее нужно было сделать только один раз.

3) Удалить Observable из class Num, избавиться от listeners,и т. д. Затем создайте несериализуемый класс, содержащий экземпляр Num.

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

Но этот способ также кажется излишне сложным. Конечно, должно быть лучшее решение? Как правильно использовать переходный процесс?

Ответы [ 2 ]

1 голос
/ 30 октября 2019

Из документации :

Однако есть странное, но хитрое решение. Используя встроенную функцию механизма сериализации, разработчики могут улучшить обычный процесс, предоставив два метода внутри своих файлов классов.

Эти методы:

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

Итак, вам просто нужно реализовать readObject, как показано ниже, и после десериализации по умолчанию просто создайте новый экземпляр HashSet. Пример:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    this.listeners = new HashSet<>();
}
0 голосов
/ 30 октября 2019

Метод 2 - хороший подход. В моей практике я всегда буду проверять наличие нуля при использовании для цикла или потока / foreach с коллекцией.

Оптимизация может быть выполнена путем инициализации набора в методе addListener, чтобы вы могли напрямую вернуться в метод набора, если коллекция являетсяempty.

Что касается производительности, оператор проверки на нуль не повлияет на производительность, поскольку метод for-loop и метод notify будут стоить больше времени, и его можно игнорировать.

...