如何确保hashCode()与equals()一致?

当重写java.lang.Object的equals()函数时,javadocs建议,

通常需要在重写此方法时覆盖hashCode方法,以便维护hashCode方法的常规协定,该方法声明相等的对象必须具有相等的哈希代码。

hashCode()方法必须为每个对象返回一个唯一的整数 (这在基于内存位置比较对象时很容易,只需返回对象的唯一整数地址)

应该如何覆盖hashCode()方法,以便它仅基于该对象的特性为每个对象返回一个唯一的整数

public class People{ public String name; public int age; public int hashCode(){ // How to get a unique integer based on name and age? } } /*******************************/ public class App{ public static void main( String args[] ){ People mike = new People(); People melissa = new People(); mike.name = "mike"; mike.age = 23; melissa.name = "melissa"; melissa.age = 24; System.out.println( mike.hasCode() ); // output? System.out.println( melissa.hashCode(); // output? } } 

它没有说对象的哈希码必须是完全唯一的,只是两个相等对象的哈希码返回相同的哈希码。 让两个不相等的对象返回相同的哈希码是完全合法的。 但是,哈希码分布在一组对象上越独特,您将从HashMaps和使用hashCode的其他操作中获得更好的性能。

诸如IntelliJ Idea之类的IDE具有用于equals和hashCode的内置生成器,它们通常在为大多数对象提供“足够好”的代码方面做得非常好(并且可能比一些手工制作的过于聪明的散列函数更好)。

例如,这是Idea为您的People类生成的hashCode函数:

 public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } 

我不会详细介绍hashCode的唯一性,因为Marc已经解决了这个问题。 对于您的People类,您首先需要确定一个人的平等意味着什么。 也许平等完全取决于他们的名字,也许是基于姓名和年龄。 它将是特定领域的。 让我们说平等是基于名称和年龄。 你被覆盖的equals看起来像

 public boolean equals(Object obj) { if (this==obj) return true; if (obj==null) return false; if (!(getClass().equals(obj.getClass())) return false; Person other = (Person)obj; return (name==null ? other.name==null : name.equals(other.name)) && age==other.age; } 

无论hashCode重写equals都必须覆盖hashCode 。 此外, hashCode在其计算中不能使用比equals更多的字段。 大多数情况下,您必须添加或排除各个字段的哈希码(hashCode应该快速计算)。 所以一个有效的hashCode方法可能如下所示:

 public int hashCode() { return (name==null ? 17 : name.hashCode()) ^ age; } 

请注意,以下内容无效,因为它使用的字段equals不(高度)。 在这种情况下,两个“等于”对象可以具有不同的哈希码。

 public int hashCode() { return (name==null ? 17 : name.hashCode()) ^ age ^ height; } 

此外,两个非等于对象具有相同的哈希码完全有效:

 public int hashCode() { return age; } 

在这种情况下,Jane年龄30不等于Bob年龄30,但他们的哈希码都是30.虽然有效,但这对于基于散列的集合中的性能是不合需要的。

另一个问题是,是否存在所有程序员都应该知道的基本低级事物,我认为哈希查找就是其中之一。 所以这里。

哈希表(注意我没有使用实际的类名)基本上是一个链表的数组。 要在表中查找内容,首先要计算该内容的哈希码,然后根据表的大小对其进行修改。 这是数组的索引,您将获得该索引的链接列表。 然后,您遍历列表,直到找到您的对象。

由于数组检索是O(1),并且链表遍历是O(n),因此您需要一个尽可能随机创建分布的哈希函数,以便将对象散列到不同的列表。 每个对象都可以返回值0作为其哈希码,并且哈希表仍然可以工作,但它本质上是数组元素0处的长链表。

您通常也希望数组很大,这会增加对象在长度为1的列表中的可能性。例如,当地图中的条目数大于75时,Java HashMap会增加数组的大小。数组大小的百分比。 这里有一个权衡:你可以拥有一个包含极少数条目和浪费内存的大型数组,或者一个较小的数组,其中数组中的每个元素都是一个包含> 1个条目的列表,并且浪费时间遍历。 完美的哈希会将每个对象分配给数组中的唯一位置,而不会浪费空间。

术语“完美散列”是一个实际术语,在某些情况下,您可以创建一个散列函数,为每个对象提供唯一的数字。 只有在知道所有可能值的集合时才可以执行此操作。 在一般情况下,您无法实现此目的,并且会有一些值返回相同的哈希码。 这是简单的数学:如果您的字符串长度超过4个字节,则无法创建唯一的4字节哈希码。

一个有趣的花絮:哈希数组通常根据素数来确定大小,以便在修改结果时为随机分配提供最佳机会,无论哈希码实际上是多么随机。

根据评论进行编辑:

1)链表不是表示具有相同哈希码的对象的唯一方法,尽管这是JDK 1.5 HashMap使用的方法。 虽然比简单数组的内存效率更低,但在重新散列时可能会产生更少的流失(因为条目可以从一个存储桶取消链接并重新链接到另一个存储桶)。

2)从JDK 1.4开始,HashMap类使用一个大小为2的幂的数组; 在此之前,它使用2 ^ N + 1,我认为这是N <= 32的素数。这不会加速数组索引本身,但允许使用按位AND而不是除法来计算数组索引,如Neil Coffey所述。 就个人而言,我认为这是过早的优化,但考虑到HashMap的作者列表,我会假设有一些真正的好处。

通常,哈希码不能是唯一的,因为存在比可能的哈希码(整数)更多的值。 良好的哈希代码可以很好地分配整数值。 一个坏的可能总是给出相同的值并且仍然在逻辑上是正确的,它只会导致无法接受的低效哈希表。

等值必须具有相同的哈希值才能使哈希表正常工作。 否则,您可以将一个键添加到哈希表中,然后尝试通过具有不同哈希码的相等值查找它,而不是找到它。 或者,您可以使用不同的哈希代码放置相等的值,并在哈希表中的不同位置具有两个相等的值。

实际上,您通常会在hashCode()和equals()方法中选择要考虑的字段子集。

我觉得你误会了。 哈希码不必对每个对象都是唯一的(毕竟,它是一个哈希码),尽管你显然不希望它对所有对象都是相同的。 但是,你需要它与所有相同的对象相同,否则像标准集合这样的东西将无法工作(例如,你在哈希集中查找某些内容但却找不到它)。

对于简单的属性,某些IDE具有哈希码function构建器。

如果您不使用IDE,请考虑使用Apahce Commons和类HashCodeBuilder

hashCode唯一的合同义务是使其保持一致 。 用于创建hashCode值的字段必须与equals方法中使用的字段相同或是其子集。 这意味着返回0表示所有值都有效,但效率不高。

可以通过unit testing检查hashCode是否一致。 我写了一个名为EqualityTestCase的抽象类,它执行一些hashCode检查。 一个人只需要扩展测试用例并实现两个或三个工厂方法。 如果hashCode有效,测试会做一个非常粗略的测试工作。

这就是文档告诉我们的哈希码方法

@javadoc

每当在执行Java应用程序期间多次在同一对象上调用它时,hashCode方法必须始终返回相同的整数,前提是不修改对象上的equals比较中使用的信息。 从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。

有一个业务键的概念,它决定了相同类型的单独实例的唯一性。 为目标域(例如车队系统中的车辆)建模单独实体的每个特定类型(类)应具有业务密钥,该业务密钥由一个或多个类字段表示。 方法equals()和hasCode()都应该使用组成业务键的字段来实现。 这确保了两种方法彼此一致。