9

假设你有一个类并且你创建了一个 HashSet 来存储这个类的实例。如果您尝试添加相等的实例,则集合中只保留一个实例,这很好。

但是,如果您在 HashSet 中有两个不同的实例,并且您将其中一个作为另一个的精确副本(通过复制字段),则 HashSet 将包含两个重复的实例。

这是演示这一点的代码:

 public static void main(String[] args)
    {
         HashSet<GraphEdge> set = new HashSet<>();
        GraphEdge edge1 = new GraphEdge(1, "a");
        GraphEdge edge2 = new GraphEdge(2, "b");
        GraphEdge edge3 = new GraphEdge(3, "c");

        set.add(edge1);
        set.add(edge2);
        set.add(edge3);

        edge2.setId(1);
        edge2.setName("a");

        for(GraphEdge edge: set)
        {
            System.out.println(edge.toString());
        }

        if(edge2.equals(edge1))
        {
            System.out.println("Equals");
        }
        else
        {
            System.out.println("Not Equals");
        }
    }

    public class GraphEdge
    {
        private int id;
        private String name;

        //Constructor ...

        //Getters & Setters...

        public int hashCode()
        {
        int hash = 7;
        hash = 47 * hash + this.id;
        hash = 47 * hash + Objects.hashCode(this.name);
        return hash;    
        }

        public boolean equals(Object o)
        {
            if(o == this)
            {
                return true;
            }

            if(o instanceof GraphEdge)
            {
                GraphEdge anotherGraphEdge = (GraphEdge) o;
                if(anotherGraphEdge.getId() == this.id && anotherGraphEdge.getName().equals(this.name))
                {
                    return true;
                }
            }

                return false;
        }
    }

上述代码的输出:

1 a
1 a
3 c
Equals

有没有办法强制 HashSet 验证其内容,以便删除在上述场景中创建的可能重复条目?

一种可能的解决方案可能是创建一个新的 HashSet 并将内容从一个哈希集复制到另一个哈希集,这样新的哈希集就不会包含重复项,但是我不喜欢这种解决方案。

4

6 回答 6

18

你描述的情况是无效的。请参阅Javadoc:“如果对象的值以影响相等比较的方式更改,而对象是集合中的元素,则不指定集合的​​行为。”

于 2012-10-28T23:29:53.693 回答
3

为了补充@EJP的答案,如果您在 a 中改变对象HashSet以使它们重复(在equals/hashcode合同的意义上),实际上会发生什么是哈希表数据结构将破坏。

  • 根据突变的具体细节和哈希表的状态,其中一个或两个实例将变得对查找不可见(例如contains和其他操作)。要么它在错误的哈希链上,要么因为另一个实例出现在它之前的哈希链上。而且很难预测哪个实例将是可见的......以及它是否会保持可见。

  • 如果您迭代该集合,两个实例仍将存在......违反Set合同。

当然,这从应用程序的角度来看是非常破碎的。


您可以通过以下任一方式避免此问题:

  • 为您的集合元素使用不可变类型,
  • 在将对象放入集合和/或将它们拉出集合时制作对象的副本,
  • 编写您的代码,以便它“知道”在持续时间内不要更改对象......

从正确性和鲁棒性的角度来看,第一种选择显然是最好的。


Incidentally, it would be really difficult to "fix" this in a general way. There is no pervasive mechanism in Java for knowing ... or being notified ... that some element has changed. You can implement such a mechanism on a class by class basis, but it has to be coded explicitly (and it won't be cheap). Even if you did have such a mechanism, what would you do? Clearly one of the objects should now be removed from the set ... but which one?

于 2012-10-29T00:09:35.023 回答
1

你是对的,我认为没有任何方法可以防止你讨论的情况。所有使用散列和等号的集合都会遇到这个问题。集合没有通知对象自添加到集合后发生了更改。我认为您概述的解决方案很好。

如果您如此关心这个问题,也许您需要重新考虑您的数据结构。例如,您可以使用不可变对象。使用不可变对象,您将不会遇到此问题。

于 2012-10-28T23:35:08.760 回答
1

HashSet在添加对象后不知道其成员的属性发生变化。如果这对您来说是个问题,那么您可能需要考虑将其设为GraphEdge不可变。例如:

GraphEdge edge4 = edge2.changeName("new_name");

在不可变的情况下GraphEdge,更改值会导致返回新实例,而不是更改现有实例。

于 2012-10-28T23:35:17.903 回答
-1

Objects.hashCode 旨在用于使用参数对象生成 hascode。您将其用作 hascode 计算的一部分。

尝试用以下代码替换您的 hashCode 实现:

public int hashCode()
{
    return Objects.hashCode(this.id, this.name);
}
于 2012-10-28T23:26:38.893 回答
-1

您需要在迭代列表时进行唯一检测。制作一个新的 HashSet 似乎不是正确的方法,但为什么不试试这个......也许不使用 HashSet 开始......

public class TestIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        list.add("1");
        list.add("1");
        list.add("2");
        list.add("3");

        for (String s : new UniqueIterator<String>(list)) {
            System.out.println(s);
        }
    }
}

public class UniqueIterator<T> implements Iterable<T> {
    private Set<T> hashSet = new HashSet<T>();

    public UniqueIterator(Iterable<T> iterable) {
        for (T t : iterable) {
            hashSet.add(t);
        }
    }

    public Iterator<T> iterator() {
        return hashSet.iterator();
    }
}
于 2012-10-28T23:45:46.187 回答