将具有相同值的数组添加到HashSet中会导致重复项

8

我正在尝试创建一组整数数组,问题是如果我尝试这样做:

HashSet<int[]> s = new HashSet<int[]>();
int a1[] = {1,2,3};
int a2[] = {1,2,3};
s.add(a1);
s.add(a2)
System.out.println(s.size());

然后s有两个对象,但应该只有一个。

注意:它不管是HashSet< Integer[]>也是一样的。它就是不能正常工作。

现在,如果我尝试使用ArrayList< Integer>来做这件事:

HashSet<ArrayList<Integer>> s = new HashSet<ArrayList<Integer>>();
ArrayList<Integer> a1 = new ArrayList<Integer>();
ArrayList<Integer> a2 = new ArrayList<Integer>();
a1.add(1);
a1.add(2);
a1.add(3);

a2.add(1);
a2.add(2);
a2.add(3);

s.add(a1);
s.add(a2)
System.out.println(s.size());

那么s就只有一个对象。

我想到了一种避免第一段代码中错误的方法,将每个数组的哈希码存储在一个哈希集合中,如下所示:

int a1[] = {0,10083,10084,1,0,1,10083,0,0,0,0};
int a2[] = {1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0,2112};
HashSet<Integer> s= new HashSet<Integer>();//hashcodes of each array
s.add(Arrays.hashCode(a1));
s.add(Arrays.hashCode(a2));
System.out.println(Arrays.hashCode(a1));
System.out.println(Arrays.hashCode(a2));
System.out.println(s.size());

它适用于第一种情况(1,2,3),但在存在碰撞的情况下,它不起作用,因此我必须处理碰撞。所以,我认为我正在自己实现一个HashSet。
使用HashSet< ArrayList< Integer>> 它完美地工作。我想Java在这种情况下管理碰撞。
我的问题是,为什么Java不允许管理一个HashSet< int[]>或HashSet< Integer[]>,如果生成的哈希码与ArrayList< Integer>中的相同,并且数组的哈希码可以通过调用Arrays.hashCode(...)来简单计算。
最后,如果我想做一个HashSet< int[]>(或HashSet< Integer[]>),我必须自己实现它吗?还是有更好的方法来做到这一点?
谢谢。

更新: 好的,最终我认为我得出了一个完整的答案。正如@ZiyaoWei和@user1676075所评论的那样,它不起作用是因为equals返回false且哈希码不同。但是,为什么Java不重写这些方法(使用Arrays.equals(),Arrays.hashCode()),以便可以做到HashSet< int[]>之类的事情呢?答案是因为数组是可变对象,并且根据哈希码的通用契约,哈希码不能取决于可变值(数组的每个元素都是可变值)。可变对象和哈希码

在这里有关于在hashCode中使用可变字段的好解释 http://blog.mgm-tp.com/2012/03/hashset-java-puzzler/ 以及在hashmaps中使用可变键的讨论 可变hashmap键是否危险?

我的答案是,如果你想使用HashSet<int[]>,你需要创建一个包含数组的类,并且如果你希望hashcode和equals依赖于值,则应该使用Arrays.equals()和Arrays.hashCode()重写这些方法。如果你不想违反契约,只需将数组设为final。

谢谢大家!


请参考下面的Ziyao的答案。简而言之,检查a.hashCode()是否与b.hashCode()匹配。我敢打赌它们不相等。 - Edward Falk
可能是重复的问题:如何在Java中创建一个数组集? - Raedwald
2个回答

8

最终与碰撞无关:

a1.equals(a2) == false

由于它们不相等,Set 将把它们视为不同。

请注意,Java 中的 Array 没有重写 Objectequals 方法。

而且,由于 Set 中的 add 定义为:

更正式地说,如果集合不包含任何元素 e2,使得 (e==null ? e2==null : e.equals(e2)),则将指定的元素 e 添加到此集合中。

似乎不可能在不违反某些契约的情况下正确实现一个可以使用 Arrays.equals 比较元素的 Set 来满足您的要求。


我觉得这是Java的一个缺陷。在我看来,Array应该重写equals方法。我找不到Effective Java中解释为什么没有这样做的参考资料。 - Philip Whitehouse
@PhilipWhitehouse 我同意,我也找不到任何解释这个主题的东西。 - zw324
嗨,谢谢。我认为这与碰撞有关。当您想要使用HashSet <CustomClass>时,必须覆盖hashCode和equals方法。因为如果它具有相同的哈希码,则必须使用equals方法来查看它们是否相同(据我所知,请纠正我!)。我认为你是对的,这是因为Java没有为数组覆盖equals方法。 :) - voodoo14
@voodoo14 不客气 :) 另外,如果您打印出数组的哈希码,它们也是不同的。Java 在数组方法上做得很差(或者我漏掉了什么)。当然,Arrays 有所帮助。 - zw324
@Ziyao Wei 哦,我使用了 Arrays.hashCode() 并得到了相同的哈希码。我忘记它会调用 a1.hashCode()。 - voodoo14

1
HashSet> 之所以有效,是因为 HashSet 将使用 .equals() 比较来确定是否两次插入相同的对象。在 List 的情况下,两个具有相同基类型(例如 ArrayList)和相同顺序的内容的列表将被视为相等。因此,您告诉 HashSet 两次插入相同的对象。它只需要一个实例一次。
当您尝试使用数组执行相同操作时,请参见此帖子:equals vs Arrays.equals in Java 了解有关 Java 中数组比较的更多详细信息。当您插入两个数组时,默认的 .equals() 测试它们是否为相同对象,但它们不是,因此失败。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接