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

时间:2014-10-04 10:12:29

标签: 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();
        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


编辑:我目前正在实施@ chiastic-security的方法。我为Foo正确地做了吗?我正在使用单独的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);
        }
    }
}

1 个答案:

答案 0 :(得分:6)

实现可能涉及循环引用的深层副本的最简单方法是,如果您希望以后能够容忍对结构的更改,则可以使用IdentityHashMapIdentityHashSet(来自here)。如果要复制:

  1. 创建一个空IdentityHashMap<Object,Object>,将源对象映射到其克隆。
  2. 创建一个空的IdentityHashSet<Object>来跟踪当前正在克隆的所有对象,但还没有完成。
  3. 开始复制过程。在每个阶段,当您要复制对象时,请在IdentityHashMap中查找它,看看您是否已经克隆了该位。如果有,请返回您在IdentityHashMap
  4. 中找到的副本
  5. 检查IdentityHashSet以查看您是否正在克隆您现在已经到达的对象(由于循环引用)。如果有,请立即将其设置为null,然后继续。
  6. 如果你以前没有克隆过这个(也就是说,源对象不在地图中),而你还没有克隆它(也就是说,它已经克隆了它)不在集合中),将其添加到IdentityHashSet,递归深度克隆它,然后当您完成递归调用时,将源/克隆对添加到IdentityHashMap,然后删除来自IdentityHashSet
  7. 现在,在递归克隆结束时,您需要处理挂起的null引用,因为您遇到了循环引用。您可以同时浏览源和目标图。每当您在源图表中找到对象时,请在IdentityHashMap中查找该对象,并找出它应映射到的内容。如果它存在于IdentityHashMap中,并且目标图中当前是null,那么您可以将目标引用设置为您在IdentityHashMap中找到的克隆。< / LI>

    这将确保您不会将图表的相同部分克隆两次,但只要图表中出现两次的对象,就会始终使用相同的参考。这也意味着循环引用不会导致无限递归。

    使用Identity版本的重点是,如果图表中的两个对象与.equals()确定的相同,但由==确定的不同实例,则为{{ 1}}和HashSet会识别这两者,并且您最终会将不应该加入的事物连接起来。 HashMap版本只会在两个实例完全相同的情况下将它们视为相同,即与Identity确定的实例相同。

    如果您想完成所有这些操作但无需亲自实施,您可以查看Java Deep Cloning Library