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

时间:2009-01-04 01:32:36

标签: java object equals hashcode

当覆盖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?
   }
}

8 个答案:

答案 0 :(得分:28)

它没有说对象的哈希码必须是完全唯一的,只是两个相等对象的哈希码返回相同的哈希码。让两个不相等的对象返回相同的哈希码是完全合法的。但是,哈希码分布在一组对象上越独特,您将从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;
}

答案 1 :(得分:9)

我不会详细介绍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;
}

每当您覆盖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;    
}

在这种情况下,简30岁不等于鲍勃30岁,但他们的哈希码都是30.虽然有效但这对基于哈希的集合的性能不利。

答案 2 :(得分:7)

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

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

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

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

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

一个有趣的小问题:哈希数组通常根据素数进行调整,以便在修改结果时为随机分配提供最佳机会,无论哈希码的实际位置是多么随机。

根据评论进行编辑:

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

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

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:0)

我认为你误解了它。哈希码不必对每个对象都是唯一的(毕竟,它是一个哈希码),尽管你显然不希望它对所有对象都是相同的。但是,你需要它与所有相同的对象相同,否则像标准集合这样的东西将不起作用(例如,你在哈希集中查找但却找不到它)。

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

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

@ javadoc

  

每当调用它时   同一个物体不止一次   执行Java应用程序,   hashCode方法必须一致   返回相同的整数,提供否   用于等比较的信息   对象被修改。这个   整数不需要保持一致   从一个应用程序的执行   到另一个执行相同的   应用

答案 7 :(得分:0)

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

相关问题