添加新元素时我应该使用克隆吗?什么时候应该使用克隆?

时间:2008-09-15 15:08:20

标签: java memory class

我想在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类。这是正确的做法还是设计从一开始就被打破了?

6 个答案:

答案 0 :(得分:4)

我在Java中使用图形结构很多,我的建议是创建Graph所依赖的Node和Edge类的任何数据成员,以保持其结构最终,没有setter。事实上,如果可以,我会使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,并建议将其作为一个很好的起点。

答案 1 :(得分:3)

我不清楚为什么要为节点添加字符串名称的附加间接。你的Edge构造函数的签名是public Edge(String, Node, Node)而不是public Edge (String, String, String)更不合理吗?

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

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

答案 2 :(得分:1)

在我看来,除非明确说明你的数据结构是这样做的,否则你不应该克隆元素。

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

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

答案 3 :(得分:1)

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

你真的想在Node上暴露一个setName()方法吗?您的示例中没有任何内容表明您需要它。如果您使Node和Edge类都不可变,那么您想象的大多数完整性违规方案都变得不可能。 (如果您需要它们是可变的,但只有在它们被添加到Graph中之后,您可以通过在Graph.Add {Node,Edge}上设置为true的Node / Edge类上具有isInGraph标志来强制执行此操作。如果在设置此标志后调用,则让mutators抛出异常。)

我同意jhkiley将Node对象传递给Edge构造函数(而不是Strings)听起来是个好主意。

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

除了@ jhkiley.blogspot.com的评论之外,您还可以为边缘和节点创建一个工厂,拒绝创建具有已使用名称的对象。