【问题标题】:Deep copy a recursive list in Java with circular references使用循环引用在 Java 中深度复制递归列表
【发布时间】:2021-05-20 19:16:47
【问题描述】:

我有课:

public class Node
{
  private String id;

  private List<Node> children;
}

我需要创建一个 List of it List 的深层副本,但考虑到可能存在循环引用,我正在尝试实现 Cloneable 接口并覆盖 clone 方法,但我不断收到 Stackoverflow 异常,所以我想知道是否有一种方法可以快速进行深度复制并消除过程中的循环依赖?

使用可克隆的类,当我尝试克隆并且它具有循环引用时,我收到错误提示

public class Node implements Cloneable
{
  private String id;

  private List<Node> children;

   @Override
    public Object clone() throws CloneNotSupportedException {
        Node clone = (Node) super.clone();
        if (children != null) {
 
            List<Node> cloneChildren = new ArrayList<>(children.size());
            for (Node child : children) {
                cloneChildren.add((Node) child.clone());
            }
            clone.setChildren(cloneChildren);
        }
        return clone;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }
}

【问题讨论】:

  • id 是否保证对每个节点都是唯一的?如果是这样,您可以编写一个复制算法来跟踪 id 并在遇到之前看到的 ID 时停止。
  • “我正在尝试实现 Cloneable 接口并覆盖克隆方法,但我不断收到 Stackoverflow 异常”——请将此代码分享为 minimal reproducible example
  • @NickReed 是的,id 是唯一的
  • 嗯,你可以跟踪在递归访问节点时遇到了哪些节点。如果已经遇到该节点,请跳过。例如,a -&gt; b -&gt; c -&gt; d -&gt; e -&gt; b...

标签: java list recursion circular-dependency circular-reference


【解决方案1】:

最简单的方法是使用IdentityHashMap&lt;Node,Node&gt; 来跟踪以前看到的Nodes,映射要复制的Nodes 和迄今为止复制的Nodes。

public Node copy() {
    Map<Node,Node> copied = new IdentityHashMap<>();
    return copy(copied, this);
}
private static Node copy(Map<Node,Node> copied, Node orig) {
    Node existing = copied.get(orig);
    if (existing != null) {
        return existing;
    }
    Node copy = new Node();
    copy.id = orig.id;
    copy.children = new ArrayList<>();
    copied.put(orig, copy);
    for (Node child : orig.children) {
        copy.children.add(copy(copied, child));
    }
    return copy;
}

(和往常一样,代码已编译但未经测试。)

我在想有一个使用 Floyd's Tortoise and Hare Algorithm 的“聪明”方法,但由于它不是单链表,所以这很复杂。

【讨论】:

  • 如果节点存在但不是循环依赖,这将如何工作?
  • 此代码仅在 Node 类实际上是 final 时才有效,也就是说,没有扩展 Node 的类。
  • @RolandIllig 如果你开始继承Node,你就会有更多的问题需要担心。假设没有其他循环机会,您可以轻松地修改此代码以使用受保护的方法,但代价是更大的接口 - 尽管我会去使类最终。我最初将它写为Node 类之外的存在,只是因为 OP 的类只有私有字段而对其进行了更改。
  • @TomHawtin-tackline 我尝试了您的解决方案,但是在执行此行 copy.children.add(copy(copied, child)); 时它再次变得循环,它创建了没有孩子的孩子,但是当添加到那里时它又是循环的:\
  • @zepol 你的意思是你的堆栈溢出了? (当然,如果您循环复制某些内容,则需要循环复制。)以下“为我工作”Node a = new Node(); Node b = new Node(); a.children.add(b); b.children.add(a); System.err.println(a.copy());
猜你喜欢
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-23
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
相关资源
最近更新 更多