【问题标题】:Should I use clone when adding a new element? When should clone be used?添加新元素时应该使用克隆吗?什么时候应该使用克隆?
【发布时间】:2010-09-08 23:45:23
【问题描述】:

我想在 Java 中实现一个用于处理图形数据结构的类。我有一个 Node 类和一个 Edge 类。 Graph 类维护两个列表:节点列表和边列表。每个节点必须有一个唯一的名称。我该如何防范这样的情况:

Graph g = new Graph();

Node n1 = new Node("#1");
Node n2 = new Node("#2");

Edge e1 = new Edge("e#1", "#1", "#2");

// Each node is added like a reference
g.addNode(n1);
g.addNode(n2);
g.addEdge(e1);

// This will break the internal integrity of the graph
n1.setName("#3");   
g.getNode("#2").setName("#4"); 

我相信我应该在将节点和边添加到图形时克隆它们并返回一个 NodeEnvelope 类,该类将保持图形结构的完整性。这是正确的做法还是设计从一开始就被破坏了?

【问题讨论】:

    标签: java memory class


    【解决方案1】:

    我在 Java 中经常使用图形结构,我的建议是使图形依赖的节点和边缘类的任何数据成员用于维护其结构是最终的,没有设置器。事实上,如果可以的话,我会让 Node 和 Edge 完全不可变,其中有 many benefits

    所以,例如:

    public final class Node {
    
        private final String name;
    
        public Node(String name) {
               this.name = name;
        }
    
        public String getName() { return name; }
        // note: no setter for name
    }
    

    然后您将在 Graph 对象中进行唯一性检查:

    public class Graph {
        Set<Node> nodes = new HashSet<Node>();
        public void addNode(Node n) {
            // note: this assumes you've properly overridden 
            // equals and hashCode in Node to make Nodes with the 
            // same name .equal() and hash to the same value.
            if(nodes.contains(n)) {
                throw new IllegalArgumentException("Already in graph: " + node);
            }
            nodes.add(n);
        }
    }
    

    如果您需要修改节点名称,请移除旧节点并添加新节点。这可能听起来像是额外的工作,但它可以节省大量精力来保持一切正常。

    不过,实际上,从头开始创建自己的 Graph 结构可能是不必要的 - 如果您自己构建,这个问题只是您可能遇到的许多问题中的第一个。

    我建议找到一个好的开源 Java 图形库,然后使用它。根据您在做什么,有一些选择。我过去曾使用过JUNG,我会推荐它作为一个很好的起点。

    【讨论】:

      【解决方案2】:

      我不清楚为什么要为节点添加额外的字符串名称间接性。将 Edge 构造函数的签名设置为 public Edge(String, Node, Node) 而不是 public Edge (String, String, String) 不是更有意义吗?

      我不知道克隆在哪里可以帮助你。

      ETA:如果危险来自在创建节点后更改节点名称,如果客户端尝试在具有现有名称的节点上调用 setName(),则抛出 IllegalOperationException

      【讨论】:

      • 我认为是一样的。这不会解决我上面说明的问题。如果在添加节点后更改名称,我仍然可以拥有两个具有相同名称的节点。
      • 抛出 IllegalOperationException 听起来是个好主意,但我仍然认为有一个更好的解决方案,从一开始就改进了类设计。我还在想。
      【解决方案3】:

      在我看来,除非您明确声明您的数据结构可以克隆该元素。

      大多数事物所需的功能需要将实际对象通过引用传递到数据结构中。

      如果您想让Node 类更安全,请将其设为图表的内部类。

      【讨论】:

      • 我已将节点类设为内部并使用接口将其暴露在外部。节点对象的任何更改也将更新图形结构。源码可以看我的博客:dev.spartancoder.com/…
      【解决方案4】:

      对我来说,使用 NodeEnvelopes 或边缘/节点工厂听起来像是过度设计。

      你真的想在 Node 上公开一个 setName() 方法吗?您的示例中没有任何内容表明您需要它。如果您将 Node 和 Edge 类都设置为不可变的,那么您设想的大多数违反完整性的场景都将变得不可能。 (如果您需要它们是可变的,但仅在它们被添加到图表之前,您可以通过在节点/边缘类上设置一个 isInGraph 标志来强制执行此操作,该标志由 Graph.Add{Node, Edge} 设置为 true,并且如果在设置此标志后调用,让您的 mutator 抛出异常。)

      我同意 jhkiley 的观点,将 Node 对象传递给 Edge 构造函数(而不是字符串)听起来是个好主意。

      如果您想要一种更具侵入性的方法,您可以使用从 Node 类返回到它所在的 Graph 的指针,并在 Node 的任何关键属性(例如名称)发生变化时更新 Graph。但我不会这样做,除非您确定需要能够更改现有节点的名称,同时保留边缘关系,这似乎不太可能。

      【讨论】:

        【解决方案5】:

        Object.clone() 存在一些重大问题,在大多数情况下不鼓励使用它。请参阅 Joshua Bloch 的“Effective Java”中的第 11 项以获得完整答案。我相信您可以安全地在原始类型数组上使用 Object.clone(),但除此之外,您需要谨慎地正确使用和覆盖克隆。您最好定义一个复制构造函数或静态工厂方法,根据您的语义显式克隆对象。

        【讨论】:

          【解决方案6】:

          除了@jhkiley.blogspot.com 的 cmets,您还可以为 Edges 和 Nodes 创建一个工厂,拒绝创建已使用名称的对象。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-05-08
            • 1970-01-01
            • 2014-12-22
            • 2016-05-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多