反序列化时如何正确使用 transient 关键字?
How to properly use transient keyword when de-serializing?
我有以下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.
}
}
}
当我序列化这个 class 时,它运行良好并且 num
被保存了。当我反序列化 class 时,num
已加载,但 listeners
未加载,而是设置为 null
。然后我的程序在 for (Observer o : listeners)
.
行崩溃
我想出了一些解决方案,但它们都是糟糕的解决方案。
1) 有一个 'reconstructs' 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
,等等。然后制作一个不可序列化的class 其中包含一个 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());
}
}
但是这种方式也显得不必要的复杂。当然必须有更好的解决方案?如何正确使用transient?
方法二是个不错的方法。在我的实践中,当使用 for 循环或流/foreach 与集合时,我将始终检查 null。
可以通过在 addListener 方法中初始化集合来进行优化,如果集合为空,则可以直接在 set 方法中return。
对于你的性能而言,空检查语句不会影响性能,因为for-loop和notify方法会花费更多时间,可以忽略。
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.
这些方法是:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException;
所以,您只需要实现如下所示的 readObject
,在默认反序列化之后,只需创建 HashSet
的新实例。示例:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.listeners = new HashSet<>();
}
我有以下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.
}
}
}
当我序列化这个 class 时,它运行良好并且 num
被保存了。当我反序列化 class 时,num
已加载,但 listeners
未加载,而是设置为 null
。然后我的程序在 for (Observer o : listeners)
.
我想出了一些解决方案,但它们都是糟糕的解决方案。
1) 有一个 'reconstructs' 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
,等等。然后制作一个不可序列化的class 其中包含一个 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());
}
}
但是这种方式也显得不必要的复杂。当然必须有更好的解决方案?如何正确使用transient?
方法二是个不错的方法。在我的实践中,当使用 for 循环或流/foreach 与集合时,我将始终检查 null。
可以通过在 addListener 方法中初始化集合来进行优化,如果集合为空,则可以直接在 set 方法中return。
对于你的性能而言,空检查语句不会影响性能,因为for-loop和notify方法会花费更多时间,可以忽略。
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.
这些方法是:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
所以,您只需要实现如下所示的 readObject
,在默认反序列化之后,只需创建 HashSet
的新实例。示例:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.listeners = new HashSet<>();
}