【问题标题】:Having some instance fields cloned by reference when deep-cloning through serialization通过序列化进行深度克隆时通过引用克隆一些实例字段
【发布时间】:2017-01-11 09:16:26
【问题描述】:

按照this answer 的建议,我正在使用序列化深度克隆一个大型 Java 类。在相应的问题中,我解释了为什么我需要以这种方式进行克隆,这突出了不同深度克隆技术结果的重要差异,即在克隆中保留共享引用,在我的情况下这是必须的。简而言之,如果在原始的两个字段中指向同一个对象,那么在深度克隆中,这些字段不应该指向两个不同的新对象,而是指向同一个新对象。通过序列化的深度克隆实现了这一点。

由于该技术需要对树中类的唯一更改是让所有类都实现Serializable,因此我不会在树的每个类中编写“克隆”方法。所以我不是在每个类中编写代码来克隆它的每个字段。但我仍然想排除克隆过程中的一些字段,为此我将transient 修饰符添加到我不想克隆的字段的声明中。这些字段在克隆中将是 null

现在我有不同的需求。我需要能够说确实必须克隆某个字段,但不能深度克隆:只需复制引用即可;让克隆中的该字段指向与原始对象相同的对象。

所以我想知道如何使序列化通过简单地复制引用而不是序列化来克隆该特定字段 - 像对其他字段一样反序列化它。这是我的问题。

否则我能想到的唯一解决方案是在树的每个类中实现一个“克隆”方法(不一定Object.clone()),并在每个“克隆”方法中显式分配每个字段,对某些字段使用序列化并复制其他字段的参考。但是除了由于要克隆的类有很多字段而导致的大量工作之外,我还担心这样我将不再保留主对象树中的共享引用,因为我会分别克隆每个字段,因此如果树中的两个字段指向同一个对象,则在克隆每个字段时将不知道这一事实,因此序列化不可能使它们指向同一个新对象。

【问题讨论】:

  • 一个想法是声明该字段瞬态,然后通过序列化克隆后复制引用。另外我相信枚举的序列化考虑到每个只能有一个实例,所以如果你可以让该字段引用的类型是枚举?
  • @OleV.V.如果我理解正确,我仍然会创建整个根对象的深层克隆,但随后将它的一些transient 字段重新分配给与原始对象相同的引用。一个问题是某些字段可能不是public 或没有设置器。我不想深度克隆某些字段的另一个原因是它们是我不需要深度克隆的大型列表,因此我想将它们从深度克隆中排除以提高性能。 Enum 点很有趣,我可以创建一些字段 Enum,但对于其他一些字段来说,这将是一个太多的 hack,会使代码太不清楚。
  • @OleV.V.抱歉,我误解了你的部分想法,所以我在评论中提到的关于性能的缺点不存在,因为你的意思是让这些字段瞬态并在克隆后分配它们,这样它们就不会被深度克隆。我实际上正在研究是否可以从克隆主对象的位置访问所有这些字段,以便在克隆后将这些字段设置为原始引用;这实际上可能解决我的问题。
  • 如果缺少公共 getter,请注意私有字段仅是类私有,而不是对象私有。因此,对象可以从属于同一类的对象的私有字段中复制引用。你可能觉得这不是最漂亮的解决方案,但它是一种选择。

标签: java serialization clone


【解决方案1】:

我想展示我在评论中提到的关于复制私有瞬态字段的想法。在这里,我对其进行了进一步的开发,引入了一个 getter 以更清楚地说明可以从对象中取出引用。请注意,getter 是私有的,也就是说,它只能从类内部调用,这足以满足我们的目的。代码已编译完毕。

public class MyClonable {

    private transient List<String> fieldRequiringShallowCopy;

    /** getter to be used by cloning */
    private List<String> getFieldRequiringShallowCopy() {
        return fieldRequiringShallowCopy;
    }

    /**
     * Call this method after cloning by serialization to copy transient field.
     * @param original The object that this object was cloned from.
     */
    public void finishClone(MyClonable original) {
        this.fieldRequiringShallowCopy = original.getFieldRequiringShallowCopy();
    }

}

编辑:要在评论中回答您的问题,如果需要浅拷贝的字段更深地嵌套在您的大对象中,您需要从外部调用并委托给持有该字段的子对象:

public class BigClonable {

    private Part enclosedThing;

    private Part getEnclosedThing() {
        return enclosedThing;
    }

    /**
     * Call this method after cloning by serialization to copy transient field.
     * @param original The object that this object was cloned from.
     */
    public void finishClone(BigClonable original) {
        // delegate to part object
        enclosedThing.finishClone(original.getEnclosedThing());
    }

}

public class Part {

    private transient List<String> fieldRequiringShallowCopy;

    /** getter to be used by cloning */
    private List<String> getFieldRequiringShallowCopy() {
        return fieldRequiringShallowCopy;
    }

    public void finishClone(Part original) {
        this.fieldRequiringShallowCopy = original.getFieldRequiringShallowCopy();
    }

}

此技术可用于任何嵌套级别。每个级别都需要一个委派方法,而我的口味也适合每个级别的私有 getter(和/或 setter)。

【讨论】:

  • 如果需要浅拷贝的私有字段不是根对象的直接字段而是字段的字段怎么办?前任根对象MyCloneable 有一个a 类型为A 的字段private transient 字段b 类型为B,这是需要浅拷贝的字段。从MyCloneable 我可以访问this.a,但不能访问this.a.b。我还应该添加到Apublic void finishClone(A original) 并从MyCloneable.finishClone 调用它吗?
  • 这似乎是我需要的一个很好的解决方案,谢谢,我会使用这个设计。理想的情况当然是能够在声明中简单地标记要浅拷贝的字段(而不必为此使用库),但是通过这种设计,我只需将它们设为 transient 并分配它们在每个具有此类字段的类中创建finishClone 方法,我可以继续使用SerializationUtils.clone(*)。对于每个需要浅拷贝的字段,我将不使用 getter,并且从 finishClone 我将访问私有字段而不是调用私有 getter。
猜你喜欢
  • 1970-01-01
  • 2013-09-06
  • 2012-02-19
  • 1970-01-01
  • 2017-09-01
  • 2012-12-25
  • 1970-01-01
  • 2016-11-20
  • 2017-09-17
相关资源
最近更新 更多