如何编写hashCode

阅读经典——《Effective Java》05

上一篇文章介绍了如何覆盖equals方法,通常在覆盖了equals方法的类中也必须覆盖hashCode方法。否则该类就无法与基于散列的集合类一起工作,比如HashMap和HashSet。至于这些集合类如何用到hashCode方法,网络上有大量说明,本文重点讨论如何编写简洁、高效的hashCode方法。

  1. hashCode通用约定
  1. 如何编写hashCode

hashCode通用约定

Java规范中要求hashCode方法必须满足:

  • 如果两个对象equals结果为真,那么他们的hashCode结果必须一致。
  • 如果两个对象equals结果为假,那么他们的hashCode结果不一定不同。但是,不同的hashCode结果有利于提高散列表性能。

如何编写hashCode

理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。虽然很难做到,但下面的方案不失为一种良好的解决方法。

  • 1.把某个非零的常数值,比如17,保存在变量int result中。
  • 2.对于对象中每个关键域f(关键域指equals方法比较时用到的域),完成以下步骤:
  • a. 为该域计算int类型的散列码c:
    • i. 如果该域是boolean类型,则计算f ? 1:0
    • ii. 如果该域是byte、char、short或者int类型,则计算(int) f
    • iii. 如果该域是long类型,则计算(int) (f ^ (f >>> 32))
    • iv. 如果该域是float类型,则计算Float.floatToIntBits(f)
    • v. 如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.a.iii,为得到的long类型值计算散列值。
    • vi. 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。
    • vii. 如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每一个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用Arrays.hashCode方法。
  • b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中。
    result = 31 * result + c;
  • 3.返回result
  • 4.写完了hashCode方法后,问问自己“相等的实例是否都具有相等的散列码”。要编写单元测试来验证你的推断。如果相等的实例有着不同的散列码,则要找出原因,并修正错误。

为了更清晰的说明这几个步骤具体如何实现,我们举一个简单的例子。考虑如下Person类的hashCode实现。

/**
 * Person并不是一个非常合适的例子,此处仅用于说明如何按照步骤编写hashCode方法。
 * 实际中一般不会按照name、gender、age来判定是否为同一个人。
 */
public class Person {

  private boolean gender;
  private int age;
  private String name;

  public Person(boolean gender, int age, String name) {
    this.gender = gender;
    this.age = age;
    this.name = name;
  }
    
  @Override
  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (!(obj instanceof Person)) return false;
    Person p = (Person) obj;
    return p.gender == gender && p.age == age && p.name == name;
  }
    
  @Override
  public int hashCode() {
    int result = 17;
    int c = gender ? 1 : 0;
    result = 31 * result + c;
    c = age;
    result = 31 * result + c;
    c = name.hashCode();
    result = 31 * result + c;
    return result;
  }
}

Person类有三个关键域gender、age和name,而且分别属于不同的类型boolean、int和String。按照前面介绍的步骤很容易实现hashCode,经过测试,相等的实例都具有相等的散列码。

关注作者或文集《Effective Java》,第一时间获取最新发布文章。

参考资料

java中HashMap详解 alex09

你可能感兴趣的:(如何编写hashCode)