深度克隆,同时保留对可变对象的共享引用

Deep-cloning while preserving shared references to mutable objects

我正在深度克隆某个 class 的实例,方法是在此 class 及其构成它的整个字段层次结构中实施 clone()。在我放入这些 classes 的 clone() 实现中,我通过在原始实例 (this) 的相应字段上调用 ​​clone() 来分配新实例的每个字段。然后我就在主 class 上调用 clone()。我相信这是一种非常标准的深度克隆方式。

下面是一个可运行的小例子。我得到的克隆是一个真正的深拷贝,其中每个字段和子字段中包含的对象都是新对象,与原始实例中的对应对象相同。

但这意味着如果在原始字段中 ab 引用同一个对象 X,在深层克隆中它们将不会引用同一个对象(X 的克隆) ;相反,他们将引用 X 的两个不同克隆。

所以我想通过深度克隆整个层次结构中的所有字段来深度克隆一个对象,但是如果层次结构在多个字段中包含相同的引用,那么这些字段中只有一个应该是深的-克隆到一个新对象中;其他字段将只引用这个新对象。

它看起来不像是一个简单的解决方案的问题,但我想知道是否存在某种技术,或者是否有某种工具或库可以做到这一点。

TestClone.java

public class TestClone {

    public static void main(String[] args) throws CloneNotSupportedException {

        // Create the object to share :

        SharedObject shared = new SharedObject(1);

        // Create the object to clone, which will own two Holder instances
        // both holding a reference to *the same* object :

        MainObject original = new MainObject(new Holder(shared), new Holder(shared));

        // Show that both holders hold a reference to the same object :

        System.out.println("Original holder1 holds " + original.holder1.field.hashCode());
        System.out.println("Original holder2 holds " + original.holder2.field.hashCode());

        // Deep-clone the main object :

        MainObject cloned = (MainObject) original.clone();

        // Show that the two cloned holders now hold a reference to *different* cloned objects :

        System.err.println("Cloned   holder1 holds " + cloned.holder1.field.hashCode());
        System.err.println("Cloned   holder2 holds " + cloned.holder2.field.hashCode());

        // How to clone so that they will hold a reference to *the same* cloned object ?
    }

}

SharedObject.java

public class SharedObject implements Cloneable {

    public int n;

    public SharedObject(int n) {

        this.n = n;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        SharedObject clone = (SharedObject) super.clone();

        clone.n = this.n;

        return clone;
    }

}

Holder.java

public class Holder implements Cloneable {

    public SharedObject field;

    public Holder(SharedObject field) {

        this.field = field;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        Holder clone = (Holder) super.clone();

        clone.field = (SharedObject) this.field.clone();

        return clone;
    }

}

MainObject.java

public class MainObject implements Cloneable {

    public Holder holder1;

    public Holder holder2;

    public MainObject(Holder holder1, Holder holder2) {

        this.holder1 = holder1;

        this.holder2 = holder2;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        MainObject clone = (MainObject) super.clone();

        clone.holder1 = (Holder) this.holder1.clone();

        clone.holder2 = (Holder) this.holder2.clone();

        return clone;
    }   

}

简答:不可能。

长答案:

这就是深度克隆的问题:您无法很好地访问它。你看,最后,克隆是由 JVM 完成的(或多或少:黑魔法)。

现在你问:我怎样才能使用那种"standard"克隆方式;但以某种方式确保克隆某些 "root object X" 的整个过程将使用某种 "caching" 机制。我注意到任何指示 JVM 这样做的现有机制。

长话短说:我认为您必须考虑其他选择;喜欢序列化成某种合理的格式;然后使用该输出。

最后引用一句伟大的answer

听起来你认为克隆是个好主意(相对于使用复制构造函数、工厂或它们的等价物)。

然后,继续:

现在,更重要的是,克隆是一个坏主意

没有"standard"这样的克隆操作方式。此外,我不知道有任何库支持它。

您的真正要求是构建从原始对象到克隆对象的一对一映射(双射)。

一种技术是首先通过克隆每个对象(如果它不在地图中)来建立这样的层次结构,一旦在您的根对象上调用了克隆方法。之后,assemble 新克隆层次结构中的引用。

该技术 - 顺便说一句 - 已经由 Java 中的序列化技术实现。让所有 类 实现 Serializable,然后将根对象写入 ObjectOutputStreampipe it to an ObjectInputStream,然后反序列化所有对象。序列化机制可以满足您的要求。