本书 Effective Java 和其他资源提供了一个很好的解释,说明在使用可序列化的Java类时如何以及何时使用readObject()方法。另一方面,readResolve()方法仍然有点神秘。基本上我发现的所有文件要么只提到两者中的一个,要么单独提及。
尚未得到答复的问题是:
我希望你能对这件事有所启发。
答案 0 :(得分:125)
readResolve
用于替换从流中读取的对象。我见过的唯一用途就是强制执行单身人士;读取对象时,将其替换为单例实例。这确保了没有人可以通过序列化和反序列化单例来创建另一个实例。
答案 1 :(得分:40)
当readResolve
从流中读取对象并准备将其返回给调用者时,将调用ObjectInputStream
方法。 ObjectInputStream
检查对象的类是否定义readResolve
方法。如果定义了方法,则调用readResolve
方法以允许流中的对象指定要返回的对象。返回的对象应该是与所有用途兼容的类型。如果它不兼容,则在发现类型不匹配时将抛出ClassCastException
。
答案 2 :(得分:27)
项目90,Effective Java,3rd Ed涵盖了readResolve
和writeReplace
的串行代理 - 它们的主要用途。这些示例不会写出readObject
和writeObject
方法,因为它们使用默认序列化来读取和写入字段。
readResolve
返回后调用 readObject
(相反writeReplace
在writeObject
之前调用,可能在另一个对象上调用)。方法返回的对象替换返回给this
用户的ObjectInputStream.readObject
对象以及对流中对象的任何进一步的后向引用。 readResolve
和writeReplace
都可以返回相同或不同类型的对象。在某些情况下,返回相同类型非常有用,其中字段必须为final
,并且需要向后兼容性或必须复制和/或验证值。
使用readResolve
不会强制执行单例属性。
答案 3 :(得分:9)
readResolve可用于更改通过readObject方法序列化的数据。对于例如xstream API使用此功能初始化一些不在要反序列化的XML中的属性。
答案 4 :(得分:5)
readResolve用于您可能需要返回现有对象的时间,例如因为您正在检查应该合并的重复输入,或者(例如,在最终一致的分布式系统中),因为它是在您了解任何旧版本之前可能会到达的更新。
答案 5 :(得分:3)
readResolve()将确保序列化时的单例合约
请 refer
答案 6 :(得分:2)
readObject()是 ObjectInputStream 类中的现有方法。在反序列化时读取对象readObject方法在内部检查是否存在具有readResolve方法的反序列化的类对象,如果存在readResolve方法然后它将调用readResolve方法并返回相同的实例。
因此,编写readResolve方法的强烈实践是实现纯单例设计模式的一种好方法,其中没有人可以通过序列化/反序列化来获得另一个实例。
答案 7 :(得分:1)
当使用序列化转换对象以便将其保存在文件中时,我们可以触发一个方法readResolve()。该方法是私有的,并保存在反序列化时检索其对象的同一个类中。
它确保在反序列化之后,返回的对象与序列化的对象相同。也就是instanceSer.hashCode() == instanceDeSer.hashCode()
readResolve()方法不是静态方法。在反序列化时调用in.readObject()
之后,它确保返回的对象与下面序列化的对象相同out.writeObject(instanceSer)
..
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
out.writeObject(instanceSer);
out.close();
通过这种方式,它还有助于单例设计模式实现,因为每次都返回相同的实例。
public static ABCSingleton getInstance(){
return ABCSingleton.instance; //instance is static
}
答案 8 :(得分:0)
readResolve方法
对于Serializable和Externalizable类,readResolve方法允许类在返回到调用者之前替换/解析从流中读取的对象。通过实现readResolve方法,类可以直接控制被反序列化的自己的实例的类型和实例。该方法定义如下:
ANY-ACCESS-MODIFIER对象readResolve() 抛出ObjectStreamException;
当 ObjectInputStream 从流中读取对象并准备将其返回给调用者时,将调用 readResolve 方法。 ObjectInputStream 检查对象的类是否定义了readResolve方法。如果定义了方法,则调用readResolve方法以允许流中的对象指定要返回的对象。返回的对象应该是与所有用途兼容的类型。如果它不兼容,则在发现类型不匹配时将抛出 ClassCastException 。
例如,可以创建 Symbol 类,在虚拟机中只存在每个符号绑定的单个实例。将实现 readResolve 方法以确定是否已定义该符号并替换预先存在的等效Symbol对象以维护标识约束。通过这种方式,可以在序列化中维护Symbol对象的唯一性。
答案 9 :(得分:0)
正如已经回答的那样,readResolve
是在反序列化对象时在ObjectInputStream中使用的私有方法。这在实际实例返回之前被调用。对于Singleton,在这里我们可以强制返回已经存在的Singleton实例引用,而不是反序列化实例引用。
类似地,我们有writeReplace
用于ObjectOutputStream。
readResolve
的示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;
public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();
private SingletonWithSerializable() {
if (INSTANCE != null)
throw new RuntimeException("Singleton instance already exists!");
}
private Object readResolve() {
return INSTANCE;
}
public void leaveTheBuilding() {
System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;
System.out.println("Before serialization: " + instance);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
out.writeObject(instance);
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
System.out.println("After deserialization: " + readObject);
}
}
}
输出:
Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
答案 10 :(得分:0)
我知道这个问题确实很老,并且已经被接受,但是当它在Google搜索中弹出时,我认为我很权衡,因为没有提供的答案涵盖了我认为重要的三种情况-在我看来用于这些方法。当然,所有人都认为实际上需要自定义序列化格式。
以收集类为例。与仅按顺序序列化元素相比,链表或BST的默认序列化将导致巨大的空间损失,而性能提升却很小。如果集合是投影或视图,则更是如此-保留对比其公共API公开的结构更大的引用。
如果序列化的对象具有需要自定义序列化的不可变字段,则writeObject/readObject
的原始解决方案是不够的,因为在之前创建了反序列化的对象,然后读取写入的流的一部分在writeObject
中。采用链表的最小实现方式:
public class List<E> extends Serializable {
public final E head;
public final List<E> tail;
public List(E head, List<E> tail) {
if (head==null)
throw new IllegalArgumentException("null as a list element");
this.head = head;
this.tail = tail;
}
//methods follow...
}
可以通过递归写入每个链接的head
字段,然后跟一个null
值来序列化此结构。然而,反序列化这种格式变得不可能:readObject
无法更改成员字段的值(现已固定为null
)。来啦
writeReplace
/ readResolve
对:
private Object writeReplace() {
return new Serializable() {
private transient List<E> contents = List.this;
private void writeObject(ObjectOutputStream oos) {
List<E> list = contents;
while (list!=null) {
oos.writeObject(list.head);
list = list.tail;
}
oos.writeObject(null);
}
private void readObject(ObjectInputStream ois) {
List<E> tail = null;
E head = ois.readObject();
if (head!=null) {
readObject(ois); //read the tail and assign it to this.contents
this.contents = new List<>(head, this.contents)
}
}
private Object readResolve() {
return this.contents;
}
}
}
很抱歉,如果以上示例未编译(或正常工作),但希望足以说明我的观点。如果您认为这是一个牵强附会的示例,请记住,许多功能语言都在JVM上运行,这种情况在他们的情况下必不可少。
我们可能想实际反序列化与写给ObjectOutputStream
的类不同的对象。诸如java.util.List
列表实现之类的视图就是这种情况,该实现公开了更长的ArrayList
中的切片。显然,序列化整个后备列表是一个坏主意,我们应该只从查看的切片中写入元素。然而,为什么要停止它并在反序列化之后具有无用的间接级别?我们可以简单地将流中的元素读入ArrayList
并直接返回它,而不必将其包装在我们的视图类中。
或者,具有专用于序列化的相似委托类可能是一种设计选择。一个很好的例子是重用我们的序列化代码。例如,如果我们有一个生成器类(类似于String的StringBuilder),我们可以编写一个序列化委托,该序列化是通过将一个空的生成器写入流中,然后集合的大小和集合的迭代器返回的元素来序列化任何集合。反序列化将涉及读取构建器,附加所有随后读取的元素,并从委托人build()
返回最终readResolve
的结果。在那种情况下,我们只需要在集合层次结构的根类中实现序列化,并且当前或将来的实现不需要抽象的iterator()
和builder()
方法(后者用于重新创建相同类型的集合-这本身就是一个非常有用的功能)。另一个示例是具有一个类层次结构,该类层次结构我们无法完全控制代码-第三方库中的基类可以具有任意数量的私有字段,而这些私有字段我们一无所知,并且可能会从一个版本更改为另一个版本,我们的序列化对象。在这种情况下,在反序列化时写入数据并手动重建对象会更安全。