【问题标题】:How to properly use transient keyword when de-serializing?反序列化时如何正确使用瞬态关键字?
【发布时间】:2020-02-25 06:39:10
【问题描述】:

我有以下课程

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)class Num 中删除Observable,去掉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,这种方法也不好,因为检查...... - 这是一个非常快速的操作
  • @ScaryWombat 是的,但是如果您考虑这些对象的生命周期,set 函数可能会在许多对象中被调用数十万次。可能有比这更好的方法来解决这个问题。
  • 他们对过早优化有什么看法?在我的代码中,我确信我有更大的问题需要考虑。
  • @ScaryWombat 我理解你的立场,但我认为这与其说是过早的优化,不如说是关于 CPU 周期意识的编码实践。
  • 无需争论 ;-) 也许其他人会插话。

标签: java design-patterns serializable transient


【解决方案1】:

来自documentation

然而,有一个奇怪而狡猾的解决方案。通过使用内置 序列化机制的特点,开发者可以增强 通过在类文件中提供两个方法来正常处理。

这些方法是:

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

【讨论】:

  • 太棒了。这正是我一直在寻找的。谢谢。
【解决方案2】:

方法 2 是一个很好的方法。在我的实践中,当使用 for 循环或流/foreach 和集合时,我总是会检查 null。

可以通过在addListener方法中初始化set来进行优化,这样如果collection为空的话可以直接在set方法中返回。

考虑到您的性能,null-check 语句不会影响性能,因为 for-loop 和 notify 方法会花费更多时间,可以忽略。

【讨论】:

  • 我明白你现在在说什么了。你是说,留下集合null,直到调用者真正想要添加一些东西。只有这样你才能初始化它并添加。我相信在这种情况下我会这样做。但是对于我所说的情况……三个对对象功能非常重要的变量呢?把他们都救下来是一种浪费。但是制作transient 会导致对象的一半是null
猜你喜欢
  • 1970-01-01
  • 2013-08-04
  • 2021-04-22
  • 2012-07-05
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 2015-10-18
  • 1970-01-01
相关资源
最近更新 更多