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

Java 深度克隆,同时保留对可变对象的共享引用,java,clone,Java,Clone,我通过在这个类及其组成它的整个字段层次结构中实现clone()来深入克隆某个类的实例。在我放在这些类中的clone()实现中,我通过调用原始(this)的相应字段上的clone()来分配新实例的每个字段。然后我就在主类上调用clone()。我相信这是一种标准的深度克隆方法 下面是一个小的可运行示例。我得到的克隆是一个真正的深度副本,其中每个字段和子字段中包含的对象都是新对象,与原始实例中的对应对象相同 但这意味着,如果在原始字段中a和b引用同一对象X,则在深度克隆中,它们将不会引用同一对象(X的

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

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

但这意味着,如果在原始字段中
a
b
引用同一对象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 ?
    }

}
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;
    }

}
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;
    }

}
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;
    }   

}
SharedObject.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 ?
    }

}
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;
    }

}
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;
    }

}
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;
    }   

}
Holder.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 ?
    }

}
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;
    }

}
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;
    }

}
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;
    }   

}
MainObject.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 ?
    }

}
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;
    }

}
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;
    }

}
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完成的(或多或少:黑魔法)

现在你问:我怎样才能使用这种“标准”的克隆方式;但不知何故确保克隆某些“根对象X”的整个过程将使用某种“缓存”机制。我注意到有任何现有的机制指示JVM这样做

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

最后,引用一个伟大的故事:

听起来你认为克隆是一个好主意(与使用复制构造函数、工厂或其等效工具相比)

并继续:

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

这种克隆操作没有“标准”方法。此外,我不知道有任何库支持这一点

您的需求实际上是构建从原始对象到克隆对象的1对1映射(双射)

一种技术是在根对象上调用clone方法后,首先通过克隆映射中不存在的每个对象来建立这样的层次结构。然后,在新的克隆层次结构中组装引用


顺便说一下,这种技术已经通过Java中的序列化技术实现了。让所有类实现
可序列化
,然后将根对象写入
对象输出流
,然后反序列化所有对象。序列化机制可以满足您的需求。

实际上,克隆是一项功能强大的技术,有它的用处。这可能是一个高级功能,如果使用不当可能会造成危害。据我所知,复制构造函数也有同样的问题。在我的复制构造函数中,我将为每个可变字段分配一个
新的
实例。因此,克隆中没有两个字段会包含相同的引用,但在原始字段中可能会包含相同的引用。事实上,我看不出,即使是工厂或其他技术也会以我需要的方式生成反映原始对象的对象,除非该技术包括检查原始字段中要克隆的字段是否包含一个引用,该引用也由层次结构中的另一个字段持有。我希望序列化能做到这一点,谢谢你提到它,我会尝试。我修改了测试代码,让
SharedObject
Holder
MainObject
都实现
Serializable
,我删除了所有
clone()
实现,在
TestClone
中,我只需调用
SerializationUtils.clone(MainObject)
即可进行克隆。当我运行它时,它显示现在克隆中的两个“持有者”持有对同一克隆
SharedObject
实例的引用。正是我需要的。因此,似乎有了这个解决方案,我走上了正确的道路。现在我将把它应用到实际代码中,其中要克隆的类当然要大得多,也更复杂,我会看看是否遇到了一些问题。很高兴能帮助您。我在实际代码中实现了您的解决方案,效果非常好。非常感谢您,特别是您提到了与其他技术不同的是,此技术不仅可以进行深度克隆,还可以在克隆中共享共享引用。我读过关于序列化的文章,特别是
SerializationUtils.clone
作为克隆的一个选项,但我没有看到任何地方提到与其他克隆技术的这一重要区别,这让我感到惊讶。