自定义 writeObject
和 readObject
方法
- 定义
writeObject
方法
- 在实现了
Serializable
接口的类中,定义一个私有方法 writeObject
,该方法接受一个 ObjectOutputStream
参数。
- 在这个方法中,手动写出对象中需要序列化的字段。对于开销较大且不常用的字段,可以选择不序列化它们,或者采用更优化的方式序列化。
- 例如:
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private String importantField;
private transient ExpensiveField expensiveField; // 假设 ExpensiveField 开销大且不常用
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(importantField);
// 可以有条件地处理 expensiveField
if (shouldSerializeExpensiveField()) {
out.writeObject(expensiveField);
}
}
}
- 定义
readObject
方法
- 同样在实现了
Serializable
接口的类中,定义一个私有方法 readObject
,该方法接受一个 ObjectInputStream
参数。
- 在这个方法中,按照
writeObject
方法写出的顺序读取对象的字段。
- 例如:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class MyComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private String importantField;
private transient ExpensiveField expensiveField;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
importantField = (String) in.readObject();
if (shouldSerializeExpensiveField()) {
expensiveField = (ExpensiveField) in.readObject();
}
}
}
关键要点
- 数据完整性
- 字段顺序:
readObject
方法中读取字段的顺序必须与 writeObject
方法中写入字段的顺序一致,否则反序列化可能失败或得到错误的数据。
- 版本控制:通过定义
serialVersionUID
来确保类的序列化版本一致性。如果类的结构发生了兼容性变化(例如添加或删除字段),需要相应地更新 serialVersionUID
,否则可能导致反序列化失败。
- 安全性
- 防止恶意数据:在
readObject
方法中,对读取的数据进行验证,防止恶意数据导致安全漏洞,如注入攻击等。
- 敏感数据处理:对于敏感字段,确保在序列化和反序列化过程中不会泄露信息。例如,可以在
writeObject
中对敏感字段进行加密,在 readObject
中进行解密。
- 高效性
- 减少不必要的序列化:如前文所述,对于开销较大且不常用的字段,可以选择不序列化或者采用更优化的方式序列化。例如,将复杂对象转换为更紧凑的表示形式进行序列化。
- 缓存和复用:在反序列化时,可以考虑缓存已经反序列化的对象,避免重复创建相同的对象,提高效率。
处理对象图中的循环引用问题
- 使用
ObjectOutputStream
和 ObjectInputStream
的内置机制:ObjectOutputStream
和 ObjectInputStream
本身就有处理循环引用的能力。当序列化一个对象图时,ObjectOutputStream
会记录已经序列化过的对象,在遇到循环引用时,不会重复序列化该对象,而是输出一个引用标记。ObjectInputStream
在反序列化时,会根据这个引用标记来重建对象图,确保循环引用的一致性。
- 自定义处理:如果需要更精细的控制,可以在类中添加额外的字段来标记对象是否已经被处理。在
writeObject
和 readObject
方法中,通过这个标记来避免重复处理已经处理过的对象,从而解决循环引用问题。例如:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Node implements Serializable {
private static final long serialVersionUID = 1L;
private String data;
private Node next;
private transient Set<Node> visited = new HashSet<>();
private void writeObject(ObjectOutputStream out) throws IOException {
if (visited.contains(this)) {
return;
}
visited.add(this);
out.writeObject(data);
if (next != null) {
out.writeObject(next);
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
if (visited.contains(this)) {
return;
}
visited.add(this);
data = (String) in.readObject();
Node nextNode = (Node) in.readObject();
if (nextNode != null) {
next = nextNode;
}
}
}