使用循环引用深度复制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 ,多个Foo


编辑 :我目前正在实施@ chiastic-security的方法。 我为Foo正确地做了吗? 我正在使用单独的HashMap来包含对象图的所有部分,以便我可以尽可能地编写深层复制function。

 Foo (Foo oldFoo) throws Exception { this(oldFoo, new IdentityHashMap(), new IdentityHashSet()); } Foo (Foo oldFoo, IdentityHashMap clonedObjects, IdentityHashSet cloning) throws Exception { System.out.println("Copying a Foo"); HashMap newToOldObjectGraph = new HashMap(); newToOldObjectGraph.put(bar, oldFoo.bar); deepCopy(newToOldObjectGraph, clonedObjects, cloning); } void deepCopy(HashMap newToOldObjectGraph, IdentityHashMap clonedObjects, IdentityHashSet cloning) throws Exception { for (Entry 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); } } } 

实现可能涉及循环引用的深层副本的最简单方法是,如果您希望以后容忍对结构的更改,则可以使用IdentityHashMapIdentityHashSet (从此处开始 )。 当你想复制时:

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

这将确保您不会两次克隆图形的相同部分,但只要在图形中出现两次的对象,它总是以相同的参考结束。 这也意味着循环引用不会导致无限递归。

使用Identity版本的要点是,如果图中的两个对象与.equals()确定的相同,但是由==确定的不同实例,那么HashSetHashMap将识别这两个对象,并且您将结束加入不应该加入的东西。 只有在相同的情况下, Identity版本才会将两个实例视为相同,即与==确定的相同。

如果您想要完成所有这些但无需自己实现,您可以查看Java深度克隆库 。