Java中hashCode和equals方法之间的关系

116

在Java中,当覆盖equals方法时,许多地方都说应该一并覆盖hashCode方法,否则就会“违反合同”。

但到目前为止,如果我只覆盖了equals方法而没有覆盖hashCode方法,我还没有遇到任何问题。

什么是这个“合同”?为什么我没有遇到问题却违反了它?如果我没有覆盖hashCode方法,在哪种情况下会出现问题?


2
这个“合约”的问题在于没有任何东西来强制执行它。如果你违反了它,没有什么会立即崩溃。但是,处理你的对象的其他代码可以自由地“崩溃”,如果你不遵循合约的规定。当你尝试在HashMap中使用这样的对象时,这正是发生的事情。 - Joachim Sauer
1
http://www.javaworld.com/article/2074996/hashcode-and-equals-method-in-java-object---a-pragmatic-concept.html - Sasikumar Murugesan
7个回答

163
你将遇到的问题与集合相关,其中元素的唯一性是根据.equals().hashCode()计算的,例如HashMap中的键。
正如其名称所示,它依赖于哈希表,哈希桶是对象的.hashCode()函数。
如果你有两个对象是.equals(),但具有不同的哈希码,那么你就会损失!
这里重要的契约部分是:具有.equals()的对象必须具有相同的.hashCode()
所有这些都在Object的javadoc文档中记录。而Joshua Bloch说你必须在Effective Java中实现它。就这样。

5
简短而言之,"+1"。 - ManishS
@fge 我有一个疑问,为什么在覆盖HashMap的hashCode方法时需要同时覆盖equals方法?无论如何,如果对象的哈希码相等,HashMap都会替换值。 - Vikas Verma
1
@VikasVerma 不是这样的;首先,计算键的哈希码,然后查询正确的桶以进行相等性比较。如果您没有覆盖.equals()方法,则可能会出现两个具有相同哈希码但不相等的对象的情况,因为.equals()的默认实现是引用相等性(即==)。 - fge
1
有一个推论:如果存在哈希冲突,则equals()的返回具有优先权。 - Davide Pugliese

17
根据文档,hashCode的默认实现会返回一些整数,每个对象之间的结果都不同。
尽可能实际,Object类定义的hashCode方法返回不同的整数用于不同的对象。(通常是通过将对象的内部地址转换为整数来实现,但这种实现技术并非Java编程语言所必须)。
然而有时您希望具有相同含义的不同对象具有相同的哈希码。例如:
Student s1 = new Student("John", 18);
Student s2 = new Student("John", 18);
s1.hashCode() != s2.hashCode(); // With the default implementation of hashCode

如果您在集合框架中使用哈希数据结构(例如 HashTable、HashSet),那么可能会出现这种问题。特别是在使用 HashSet 等集合时,您可能会遇到重复元素并违反 Set 的约定。

11

是的,它应该被覆盖。如果您认为需要重写equals(),那么您需要重写hashCode(),反之亦然。 hashCode() 的一般契约是:

  1. 在Java应用程序的执行过程中,如果同一对象被多次调用,则hashCode方法必须始终返回相同的整数,前提是未修改用于在对象上进行equals比较的任何信息。这个整数不需要从一个应用程序的执行到另一个应用程序的执行保持一致。

  2. 如果两个对象根据equals(Object)方法是相等的,则在每个对象上调用hashCode方法必须产生相同的整数结果。

  3. 不要求如果根据equals(java.lang.Object)方法两个对象不相等,则在每个对象上调用hashCode方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。


8
请看一下 HashtablesHashmapsHashSets 等等。它们都将散列后的键作为它们的键存储。在调用 get(Object key) 时,参数的哈希值将被生成并在给定的哈希值中查找。
当没有重写 hashCode() 并且键的实例已被更改(例如一个简单的字符串根本不重要),hashCode() 可能会对同一对象产生两个不同的哈希码,导致无法在 map.get() 中找到您提供的键。

7

这个约定是如果obj1.equals(obj2),那么obj1.hashCode() == obj2.hashCode(),主要是为了提高性能,因为映射(maps)主要使用hashCode方法来比较条目(entries)的键(keys)。


1
如果 obj1 != obj2,则 obj1.hasCode() != obj2.hashCode() <-- 不正确!它们可能具有不同的哈希码,但这不是必需的。 - fge
1
@fge 你说得对,哈希码可能会发生碰撞,我已经进行了编辑。 - gma

6

查看 Java.lang.Object的JavaDoc文档

hashCode()中,它说:

如果两个对象根据equals(Object)方法是相等的,则在这两个对象上调用hashCode方法必须产生相同的整数结果

(由我强调)。

如果您仅覆盖equals()而不是hashCode(),则您的类将违反此契约。

这也在equals()方法的JavaDoc文档中提到:

请注意,通常需要覆盖hashCode方法,以便在覆盖此方法时维护hashCode方法的一般契约,该契约规定相等的对象必须具有相等的哈希码。

5

一个合同是:如果两个对象相等,则它们应该具有相同的哈希码;如果两个对象不相等,则它们可能具有相同的哈希码,也可能没有。

尝试将您的对象用作HashMap中的键(在joachim-sauer的评论后编辑),您将开始遇到麻烦。合同是一种指导方针,而不是强制性规定。


"should have" => "必须有",你应该提到“尝试将它们用作HashMap中的”。 - Joachim Sauer

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