使用循环引用深度复制Java对象

使用循环引用深度复制Java对象,java,Java,如何为Foo实现深度拷贝?它包含一个Bar的实例,然后它引用了该Foo public class Foo { Bar bar; Foo () { bar = new Bar(this); } Foo (Foo oldFoo) { bar = new Bar(oldFoo.bar); } public static void main(String[] args) { Foo foo = new

如何为
Foo
实现深度拷贝?它包含一个
Bar
的实例,然后它引用了该
Foo

public class Foo {

    Bar bar;

    Foo () {
        bar = new Bar(this);
    }

    Foo (Foo oldFoo) {
        bar = new Bar(oldFoo.bar);
    }

    public static void main(String[] args) {
        Foo foo = new Foo();
        Foo newFoo = new Foo(foo);
    }

    class Bar {
        Foo foo;

        Bar (Foo foo) {
            this.foo = foo;
        }

        Bar (Bar oldBar) {
            foo = newFoo(oldbar.Foo);
        }
    }
}
目前,由于无限递归,此代码将导致堆栈溢出

而且,这是我能构造的最简单的例子。实际上,对象图会更大,有多个实例变量,它们本身可能是集合。想想多个
Bar
s,比如多个
Foo
s


编辑:我目前正在实施@chiatic security的方法。我为福做的对吗?我使用一个单独的HashMap来包含对象图的所有部分,这样我就可以尽可能地编写深度复制功能

Foo (Foo oldFoo) throws Exception {
    this(oldFoo, new IdentityHashMap<Object, Object>(), new IdentityHashSet<Object>());
}

Foo (Foo oldFoo, IdentityHashMap<Object, Object> clonedObjects, IdentityHashSet<Object> cloning) throws Exception {
    System.out.println("Copying a Foo");

    HashMap<Object, Object> newToOldObjectGraph = new HashMap<Object, Object>();
    newToOldObjectGraph.put(bar, oldFoo.bar);
    deepCopy(newToOldObjectGraph, clonedObjects, cloning);
}

void deepCopy(HashMap<Object, Object> newToOldObjectGraph, IdentityHashMap<Object, Object> clonedObjects, IdentityHashSet<Object> cloning) throws Exception {
    for (Entry<Object, Object> entry : newToOldObjectGraph.entrySet()) {
        Object newObj = entry.getKey();
        Object oldObj = entry.getValue();

        if (clonedObjects.containsKey(oldObj)) {
            newObj = clonedObjects.get(oldObj);
        }
        else if (cloning.contains(oldObj)){
            newObj = null;
        }
        else {
            cloning.add(oldObj);
            // Recursively deep clone
            newObj = newObj.getClass().getConstructor(oldObj.getClass(), clonedObjects.getClass(), cloning.getClass()).
                newInstance(oldObj, clonedObjects, cloning);

            clonedObjects.put(oldObj, newObj);
            cloning.remove(oldObj);
        }
        if (newObj == null && clonedObjects.containsKey(oldObj)) {
            newObj = clonedObjects.get(oldObj);
        }
    }
}
Foo(Foo oldFoo)引发异常{
这(oldFoo,newidentityhashmap(),newidentityhashset());
}
Foo(Foo oldFoo,IdentityHashMap clonedObjects,IdentityHashSet cloning)引发异常{
System.out.println(“复制一个Foo”);
HashMap newToOldObjectGraph=新HashMap();
newToOldObjectGraph.put(bar,oldFoo.bar);
deepCopy(NewToolObjectGraph、clonedObjects、克隆);
}
void deepCopy(HashMap newToOldObjectGraph、IdentityHashMap clonedObjects、IdentityHashSet cloning)引发异常{
for(条目:newToOldObjectGraph.entrySet()){
Object newObj=entry.getKey();
Object oldObj=entry.getValue();
if(clonedObjects.containsKey(oldObj)){
newObj=clonedObjects.get(oldObj);
}
else if(cloning.contains(oldObj)){
newObj=null;
}
否则{
克隆.add(oldObj);
//递归深度克隆
newObj=newObj.getClass().getConstructor(oldObj.getClass(),clonedObjects.getClass(),cloning.getClass())。
newInstance(oldObj、clonedObjects、克隆);
clonedObjects.put(oldObj,newObj);
克隆。删除(oldObj);
}
if(newObj==null&&clonedObjects.containsKey(oldObj)){
newObj=clonedObjects.get(oldObj);
}
}
}

实现可能涉及循环引用的深度副本的最简单方法是使用
IdentityHashMap
IdentityHashSet
(来自)。当您要复制时:

  • 创建一个空的
    IdentityHashMap
    ,将源对象映射到它们的克隆
  • 创建一个空的
    IdentityHashSet
    ,以跟踪当前正在克隆但尚未完成的所有对象
  • 开始复制过程。在每个阶段,当您想要复制一个对象时,请在
    IdentityHashMap
    中查找该对象,查看您是否已经克隆了该位。如果有,请返回在
    IdentityHashMap
    中找到的副本
  • 检查代码>标识THYTHASSET < /代码>,看看你是否正在克隆你现在已经达到的对象(因为循环引用)。如果有,现在只需将其设置为
    null
    ,然后继续
  • 如果您以前没有克隆过这个(即源对象不在映射中),并且您不在克隆它的中间(即,它不在集合中),将其添加到<代码> IddiTyHasset < /C>中,递归地深克隆它,然后当您完成递归调用时,将源/克隆对添加到<代码>标识THYTHASMAP> <代码>,并将其从
    IdentityHashSet
    中删除
  • 现在,在递归克隆结束时,您需要处理由于遇到循环引用而挂起的
    null
    引用。您可以同时浏览源和目标的图形。每当您在源图形中找到对象时,请在
    IdentityHashMap
    中查找它,并找出它应该映射到的对象。如果它存在于
    IdentityHashMap
    中,并且当前在目标图中为
    null
    ,则可以将目标引用设置为在
    IdentityHashMap
    中找到的克隆
  • 这将确保您不会克隆图形的同一部分两次,但每当图形中出现两次对象时,总是以相同的引用结束。这也意味着循环引用不会导致无限递归

    使用
    标识
    版本的要点是,如果图形中的两个对象与
    .equals()
    确定的对象相同,但由
    =
    确定的实例不同,则
    哈希集
    哈希映射
    将标识这两个对象,你会把不该连接的东西连接在一起。
    标识
    版本仅当两个实例相同时才会将其视为相同,即,由
    ==
    确定的相同实例


    如果你想做所有这些,但不必自己实现,你可以看看。

    好吧,使用任何参数化构造函数都可以做到这一点,所以我认为重新设计是合适的。如果你最终不想自己实现,有一些库会处理这一点,例如,Dozer:您必须将集合传递到深度递归克隆方法中。此外,使用HashSet也不安全,因为您可能有等于()但不相同的对象。@jtahlborn是对的。抢手货您需要一个
    标识hashset
    。我将相应地修改我的答案。为什么我们需要跟踪正在被复制的对象?一旦我们遇到他们,他们的副本就可以合法引用,因此不需要HashSet。IdentityHashMap会更安全。@basilev好吧,这取决于API。如果您对所有内容都没有arg构造函数,那么您是对的,您只需要
    HashMap
    ,因为一旦您获得了一个引用,您就可以进行排序了。但是如果您的实例是使用const创建的