Java 序列化: readObject() vs

有效的爪哇和其他资源提供了很好的解释,说明了在使用可序列化的 Java 类时如何以及何时使用 readObject()方法。另一方面,readResolve()方法仍然有点神秘。基本上,我发现的所有文件要么只提到其中一个,要么只单独提到两个。

尚未得到解答的问题有:

  • 这两种方法的区别是什么?
  • 什么时候应该实现哪种方法?
  • 如何使用 readResolve(),尤其是返回什么?

我希望你能解释一下这件事。

101730 次浏览

readResolve用于从流中读取的对象 替换。我所见过的唯一用途是强制使用单例; 当读取一个对象时,将其替换为单例实例。这确保没有人可以通过序列化和反序列化单例来创建另一个实例。

项目90,有效的 Java,第三版涵盖了 readResolvewriteReplace的串行代理-它们的主要用途。这些示例不写出 readObjectwriteObject方法,因为它们使用默认序列化来读写字段。

readResolvereadObject返回之后调用(相反,writeReplacewriteObject之前调用,可能在不同的对象上调用)。该方法返回的对象将替换返回给 ObjectInputStream.readObject用户的 this对象以及对流中对象的任何进一步反向引用。readResolvewriteReplace都可以返回相同或不同类型的对象。在某些情况下,返回相同的类型非常有用,因为字段必须是 final,并且需要向下兼容,或者必须复制和/或验证值。

使用 readResolve并不强制使用 singleton 属性。

ReadResolve ()将在序列化时确保单例契约。
网址: http://www.javalobby.org/java/forum/t17491.html < BR >

ReadResolve 可用于更改通过 readObject 方法序列化的数据。例如,xstream API 使用这个特性来初始化一些不在要反序列化的 XML 中的属性。

Http://x-stream.github.io/faq.html#serialization

ReadResolve 用于当您可能需要返回一个现有的对象时,例如,因为您正在检查应该合并的重复输入,或者(例如,在最终一致的分布式系统中)因为这是一个可能在您意识到任何旧版本之前到达的更新。

ReadResolve 方法

对于可串行化和可外部化的类,readResolve 方法允许类在从流中读取的对象返回给调用者之前替换/解析它。通过实现 readResolve 方法,类可以直接控制正在反序列化的自身实例的类型和实例。该方法的定义如下:

Any-Access-修饰符对象 readResolve () 抛出 ObjectStreamException;

ObjectInputStream从流中读取对象并准备将其返回给调用方时,将调用 重新解决方法。ObjectInputStream检查对象的类是否定义 readResolve 方法。如果定义了该方法,则调用 readResolve 方法以允许流中的对象指定要返回的对象。返回的对象应该具有与所有用途兼容的类型。如果它不兼容,当发现类型不匹配时将引发 ClassCastException

例如,可以创建一个 符号类,其中每个符号绑定在虚拟机中只有一个实例。将实现 重新解决方法以确定该符号是否已经定义,并替换先前存在的等效符号对象以维护标识约束。通过这种方式,可以在整个序列化过程中保持符号对象的唯一性。

当序列化用于转换对象以便将其保存在文件中时,我们可以触发一个方法 readResolve ()。该方法是私有的,并且保存在反序列化过程中检索对象的同一个类中。 它确保在反序列化之后,返回的对象与序列化后的对象相同,即 instanceSer.hashCode() == instanceDeSer.hashCode()

ReadResolve ()方法不是静态方法。反序列化时调用 in.readObject()之后,它只是确保返回的对象与以下反序列化时的对象相同

..
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
out.writeObject(instanceSer);
out.close();

通过这种方式,它还有助于 单件设计模式的实现,因为每次返回相同的实例。

public static ABCSingleton getInstance(){
return ABCSingleton.instance; //instance is static
}

readObject()ObjectInputStream类中的一个现有方法。 在反序列化时,readObject()方法在内部检查正在反序列化的对象是否实现了 readResolve()方法。如果 readResolve()方法存在,那么它将被调用

样例 readResolve()实现如下所示

protected Object readResolve() {
return INSTANCE:
}

因此,编写 readResolve()方法的目的是确保返回 JVM 中的相同对象,而不是在反序列化过程中创建新对象。

如前所述,readResolve是 ObjectInputStream 在反序列化对象时使用的私有方法。这在返回实际实例之前调用。对于 Singleton,这里我们可以强制返回已经存在的单例实例引用,而不是反序列化的实例引用。 类似地,我们为 ObjectOutputStream 提供了 writeReplace

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

我知道这个问题很老了,而且已经有了一个公认的答案,但是由于它在谷歌搜索中出现的频率很高,我想我应该参与进来,因为没有提供的答案涵盖了我认为重要的三种情况——在我看来这些方法的主要用途。当然,所有这些都假设实际上需要自定义序列化格式。

以集合类为例。与按顺序序列化元素相比,链表或 BST 的默认序列化将导致巨大的空间损失,性能增益非常小。如果一个集合是一个投影或视图——保留一个比它的公共 API 所公开的更大的结构的引用,那么这种情况就更加真实了。

  1. 如果序列化对象具有需要自定义序列化的不可变字段,则 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...
    }
    

This structure can be serialized by recursively writing the head field of every link, followed by a null value. Deserializing such a format becomes however impossible: readObject can't change the values of member fields (now fixed to null). Here come the writeReplace/readResolve pair:

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 上运行的,这种方法在它们的情况下是必不可少的。

  1. 我们可能希望实际反序列化一个与写入 ObjectOutputStream的类不同的对象。对于诸如 java.util.List列表实现之类的视图来说,情况就是这样,它从较长的 ArrayList中暴露出一个片。显然,序列化整个后备列表是一个糟糕的主意,我们应该只编写来自被查看的切片的元素。然而,为什么停在它和有一个无用的反序列化后的间接水平?我们可以简单地将流中的元素读取到 ArrayList中,然后直接返回它,而不是将它封装在视图类中。

  2. 或者,可以选择使用一个专门用于序列化的类似委托类。一个很好的例子就是重用我们的序列化代码。例如,如果我们有一个构建器类(类似于 StringBuilder For String) ,我们可以编写一个序列化委托,通过向流中写入一个空的构建器来序列化任何集合,然后写入集合大小和由集合迭代器返回的元素。反序列化包括读取构建器、追加所有随后读取的元素,并从代理 readResolve返回最终 build()的结果。在这种情况下,我们只需要在集合层次结构的根类中实现序列化,当前或未来的实现不需要额外的代码,前提是它们实现抽象 iterator()builder()方法(后者用于重新创建相同类型的集合——这本身就是一个非常有用的特性)。另一个例子是我们不能完全控制的类层次结构——来自第三方库的基类可能有我们不知道的任意数量的私有字段,这些私有字段可能从一个版本变成另一个版本,破坏我们的序列化对象。在这种情况下,在反序列化时手动编写数据和重新构建对象会更安全。