理解Java HashSet的contains方法

17

Java HashSet的新手问题

Set<User> s = new HashSet<User>();
User u = new User();
u.setName("name1");
s.add(u);
u.setName("name3");
System.out.println(s.contains(u));

有人能解释一下为什么这段代码输出false吗?此外,这段代码甚至没有调用User类的equals方法。但根据HashSet和HashMap的源码,它应该调用它。 User类的equals方法只是在用户的名称上调用equals方法。Method hashCode返回用户名称的hashCode。


引用Jon Skeet的话,“哈希集合中的对象应该是不可变的,或者在使用完哈希集合(或哈希映射)中的对象后,您需要自律,不要改变它们。”- https://dev59.com/ZG445IYBdhLWcg3wustT - Qwerky
2个回答

15
如果哈希码方法基于name字段,然后在添加对象后更改该字段,那么第二个contains检查将使用新的哈希值,找不到您正在查找的对象。这是因为HashSet首先按哈希码搜索,所以如果搜索失败,它们就不会调用equals方法。
唯一能够正常工作的方式是,如果您没有覆盖equals方法(因此使用默认的引用相等性)并且幸运地两个对象的哈希码相等。但这是非常不可能的情况,您不应该依赖它。
通常情况下,如果更改对象后会更改其哈希码,则绝不能将其添加到HashSet中。

那么这是将 u 的副本添加到哈希集中吗?否则我会认为 nameset 中的对象也将具有 name3。 - Ced

11

由于你的新User拥有一个不同的哈希码,HashSet知道它们不相等。

HashSet根据其哈希码存储项。
当HashSet找到具有相同哈希码的项时,它只会调用equals方法,以确保这两个项实际上是相等的(而不是哈希冲突)。


1
实际上,只有在 hashCode 相等的情况下才会调用 equals。这意味着,如果我更新了 User,改变了它的 hashCode,包含与其 hashCode 相关联的 Entry 的嵌套数组将不会被更新。因此,遍历该数组将不会返回具有相同 hashCode 的 entry。如果 hashCode 始终返回0,则应该可以正常工作。 - user12384512
3
通常,将可变对象放入哈希集合中是不明智的。如果让hashCode()返回值为0,将失去哈希集合的所有性能优势,你最终会得到可能最慢的集合。 - SLaks
我知道,这只是一个例子。 - user12384512
我相信这应该是被接受的答案。值得注意的是,HashSet.contains() 的 javadoc 并没有说 equals 只有在检查 hashcode 后才被应用。 - Felipe Leão

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