我如何为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);
}
}
}
答案 0 :(得分:6)
实现可能涉及循环引用的深层副本的最简单方法是,如果您希望以后能够容忍对结构的更改,则可以使用IdentityHashMap
和IdentityHashSet
(来自here)。如果要复制:
IdentityHashMap<Object,Object>
,将源对象映射到其克隆。IdentityHashSet<Object>
来跟踪当前正在克隆的所有对象,但还没有完成。IdentityHashMap
中查找它,看看您是否已经克隆了该位。如果有,请返回您在IdentityHashMap
。IdentityHashSet
以查看您是否正在克隆您现在已经到达的对象(由于循环引用)。如果有,请立即将其设置为null
,然后继续。IdentityHashSet
,递归深度克隆它,然后当您完成递归调用时,将源/克隆对添加到IdentityHashMap
,然后删除来自IdentityHashSet
。null
引用,因为您遇到了循环引用。您可以同时浏览源和目标图。每当您在源图表中找到对象时,请在IdentityHashMap
中查找该对象,并找出它应映射到的内容。如果它存在于IdentityHashMap
中,并且目标图中当前是null
,那么您可以将目标引用设置为您在IdentityHashMap
中找到的克隆。< / LI>
醇>
这将确保您不会将图表的相同部分克隆两次,但只要图表中出现两次的对象,就会始终使用相同的参考。这也意味着循环引用不会导致无限递归。
使用Identity
版本的重点是,如果图表中的两个对象与.equals()
确定的相同,但由==
确定的不同实例,则为{{ 1}}和HashSet
会识别这两者,并且您最终会将不应该加入的事物连接起来。 HashMap
版本只会在两个实例完全相同的情况下将它们视为相同,即与Identity
确定的实例相同。
如果您想完成所有这些操作但无需亲自实施,您可以查看Java Deep Cloning Library。