【问题标题】:Effective Java Item #77 - Serialization of singleton objects - Why should I have to use readResolve?Effective Java Item #77 - 单例对象的序列化 - 为什么我必须使用 readResolve?
【发布时间】:2012-05-08 14:18:20
【问题描述】:

Effective Java #77 声明我们必须使用readResolve 在序列化期间保留单例保证。他们已经使用了这个例子。

public class Elvis implements Serializable{
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }

他们建议使用

如果猫王类是 实现可序列化, 以下 readResolve 方法就足够了 保证单例属性:

// readResolve for instance control - you can do better!
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE; }

此方法忽略 反序列化的对象,返回 杰出的猫王实例是 类创建时创建 初始化。

  • 现在的问题是 序列化再次加载类 有两个猫王的实例?
  • 如果类只加载一次 那么我们应该只有一个 猫王的实例,因为静态 字段未序列化并且是 期间未恢复 反序列化和
  • 另一个猫王从哪里来 实例来了 有资格进行垃圾收集 readResolve(防止 逃避反序列化 过程)。 可以解释一下吗?

【问题讨论】:

    标签: java serialization singleton


    【解决方案1】:
    • 该类只加载一次(除非您使用类加载器,但实际上这些类是不同的)。上述代码的反序列化确实创建了一个新的Elvis。您需要使用串行代理来避免这种情况。

    • 虽然有两个Elvis 实例,但Elvis.INSTANCE 只指向一个。

    • 反序列化构造对象而不调用执行任何可序列化类的构造函数(但它会执行最派生的不可序列化基类的无参数构造函数)。

    (顺便说一句,你还没有创建你的类final,所以即使是一个子类也可以反序列化。顺便提一下,你提出的readResolve 方法不会因为它是private 而被子类调用。 )

    【讨论】:

    • @Tom - 根据对象序列化规范,readResolve 方法可以有任何访问修饰符。可以是private
    • @Stephen 我认为超类中的私有方法不是这样
    • @Stephen C “如果该方法存在并且可以从被序列化对象的类中定义的方法访问,则该 writeReplace 方法由序列化调用。” [来自java.io.Serializable] 因此,对于子类,私有方法将无法访问(它也不会遵循嵌套类的规则......)。
    • @Tom - “上述代码的反序列化确实创建了一个新的猫王” - 你的意思是创建了一个猫王类型的新对象,并且所有非静态和非瞬态字段都填充了反序列化过程中的值?在那种情况下,我们如何为公共静态最终归档实例提供两个值?
    • @tutysara INSTANCE 仅指向一个实例。 Elvis 的一个实例是原始实例,另一个是您通常期望从反序列化过程中得到的实例,它调用了 readResolve(除非它是子类)。
    【解决方案2】:

    我建议在单例类中创建一个代理类来保存所有变量。然后你可以拥有访问函数 [getProxyClass() & setProxyClass()]。使代理类可序列化,然后当您进行序列化或反序列化时使用代理类,只需使用访问器函数来获取或设置它。如果你这样做,它会减少单例类所涉及的很多混乱。

    【讨论】:

      【解决方案3】:
      • 现在的问题是序列化是否会再次加载类以拥有两个 Elvis 实例?

      创建了一个新的 Elvis 实例(模仿者),但重载的 readResolve 方法确保它不会作为 ObjectInputStream.readObject() 返回的数据结构的一部分返回。

      猫王模仿者(或很快变得)无法访问,并且被垃圾收集。

      • 如果类只加载一次,那么我们应该只有一个 Elvis 实例,因为静态字段没有序列化,也不会在反序列化过程中恢复,并且

      理想情况下是的。在实践中没有。

      • 另一个 Elvis 实例来自哪里,它可以通过 readResolve 进行垃圾收集(防止逃避反序列化过程)。可以解释一下吗?

      反序列化过程从创建第二个 Elvis(模仿者)开始,但 readResolve 方法确保没有人看到它。

      要了解这种情况的发生方式和原因,您需要了解readResolve() 方法在反序列化中发挥的功能,如指定的here。基本上,当readResolve() 方法返回INSTANCE 时,它是在说,“无论您打算在我们构建的图表中的何处使用模仿者,请改用真正的猫王”。

      【讨论】:

      • @Stephen C 猫王模仿者变得无法访问,除非对手首先获取对它的引用。
      • @Stephen C - “反序列化过程从创建第二个 Elvis(模仿者)开始” - 它是否创建另一个对象并为静态字段 INSTANCE 分配新值?静态字段没有被序列化,它们在加载类加载时被初始化。
      • @Tom - 我以为我就是这么说的。
      • @tutysara - 是的,创建了第二个猫王。不,它不会将新的 Elvis 分配给静态对象。正常的对象序列化机制不知道静态,并且 Elvis 的特定于类的钩子(例如它的 readResolve 方法)不会分配给 INSTANCE
      • @Stephen C 你说“是(或很快变得)无法访问”,我说不一定如此。 (另外readResolve (不一定)被重载甚至覆盖。)
      猜你喜欢
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 2019-03-31
      • 1970-01-01
      • 2015-12-29
      • 1970-01-01
      • 2011-02-03
      相关资源
      最近更新 更多