Java 添加新元素时是否应使用克隆?什么时候应该使用克隆?

Java 添加新元素时是否应使用克隆?什么时候应该使用克隆?,java,memory,class,Java,Memory,Class,我想在Java中实现一个用于处理图形数据结构的类。我有一个节点类和一个边类。Graph类维护两个列表:节点列表和边列表。每个节点必须具有唯一的名称。我如何防范这种情况: 我认为在将节点和边添加到图中时应该克隆它们,并返回一个NodeDevelope类,该类将保持图的结构完整性。这是正确的方法还是设计从一开始就被破坏了?在我看来,除非您明确声明您的数据结构会这样做,否则您永远不应该克隆元素 大多数事物所需的功能需要通过引用将实际对象传递到数据结构中 如果要使节点类更安全,请使其成为图形的内部类

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


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

在我看来,除非您明确声明您的数据结构会这样做,否则您永远不应该克隆元素

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


如果要使
节点
类更安全,请使其成为图形的内部类。

我不清楚为什么要为节点添加字符串名称的附加间接方向。如果边缘构造函数的签名类似于
公共边缘(String,Node,Node)
而不是
公共边缘(String,String,String)
,这不是更有意义吗

我不知道克隆人在哪里能帮到你


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

除了@jhkiley.blogspot.com的注释之外,您可以为拒绝创建已使用名称的对象的边和节点创建工厂。

我经常使用Java中的图形结构,我的建议是使图形维护其结构所依赖的节点和边类的任何数据成员都是最终的,而不使用任何设置器。事实上,如果可以的话,我会使Node和Edge完全不可变,这已经实现了

例如:

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
}
然后在图形对象中执行唯一性检查:

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);
    }
}
公共类图{
Set nodes=new HashSet();
公共void addNode(节点n){
//注意:这假设您已经正确地覆盖了
//在节点中使用equals和hashCode生成具有
//相同的name.equal()和哈希为相同的值。
if(nodes.contains(n)){
抛出新的IllegalArgumentException(“已在图形中:+节点”);
}
节点。添加(n);
}
}
如果需要修改节点的名称,请删除旧节点并添加新节点。这听起来像是额外的工作,但它节省了很多努力保持一切正常

但是,实际上,从头开始创建自己的图形结构可能是不必要的——如果您构建自己的图形结构,那么这个问题只是您可能遇到的许多问题中的第一个


我建议找到一个好的开源Java图形库,并改用它。根据你正在做的事情,有几个选择。我过去曾使用过,并建议将其作为一个良好的起点。

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

是否确实要在节点上公开setName()方法?在你的例子中没有任何东西表明你需要它。如果将节点类和边缘类都设置为不可变的,那么您所设想的大多数完整性冲突场景将变得不可能。(如果您需要它们是可变的,但只有在它们被添加到图中之前,您可以通过在节点/边类上设置一个isInGraph标志来实现这一点,该标志由Graph设置为true。添加{Node,Edge},如果在设置该标志后调用,则让您的变体引发异常。)

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


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

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

我认为这是一样的。这并不能解决我上面提到的问题。如果在添加节点后更改名称,我仍然可以使用相同名称的两个节点。抛出IllegalOperationException听起来是个好主意,但我仍然认为从一开始就改进类设计有更好的解决方案。我还在想,我把节点类放在了内部,并使用接口将其暴露在了外部。节点对象上的任何更改也将更新图形结构。您可以在我的博客上看到源代码:
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);
    }
}