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

当重写java.lang.Object的equals()函数时,javadoc提示,

通常需要在重写此方法时重写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唯一性的细节。 对于您的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.虽然有效,但这对于基于哈希的集合中的性能是不合需要的。


另一个问题是询问是否有所有程序员都应该知道的基本底层事情,我认为散列查找就是其中之一。 所以在这里。

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

由于数组检索是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而不是除法来计算数组索引,正如尼尔科菲所指出的那样。 就我个人而言,我会质疑这是过早的优化,但考虑到HashMap上的作者列表,我会假设有一些真正的好处。

链接地址: http://www.djcxy.com/p/71621.html

上一篇: How to ensure hashCode() is consistent with equals()?

下一篇: Python memory allocation error using subprocess.Popen