一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。这正是hashCode约定中的第三条的含义。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。要想完全达到这种理想的情形是非常困难的。幸运的是,相对接近这种理想情形则并不太困难。下面给出一种简单的解决方法:
- 把某个非零的常数值,比如说17,保存在一个名为result的int类型变量中。
- 对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤:
- 为该域计算int类型的散列码c:
- 如果该域是boolean类型,则计算(f ? 1:0)。
- 如果该域是byte、char、short或者int类型,则计算(int)f。
- 如果该域是long类型,则计算(int)(f^(f>>>32))。
- 如果该域是float类型,则计算Float.floatToIntBits(f)。
- 如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.i.c,为得到的long类型值计算散列值。
- 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归的调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式(canonical representation)”,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数,但通常是0)。
- 如果该域是一个数组,则要把每一个元素当作单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.2中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法。
- 按照下面的公式,把步骤2.i中计算得到的散列码c合并到result中:
> 其中的常质数31的选择大有讲究。简单来讲,既可以进一步打散散列码,又可以通过编译器优化为(result<<5-result)加快计算速度。
- 为该域计算int类型的散列码c:
result = 31 * result +c
- 返回result。
- 写完了hashCode方法之后,问问自己“相等的实例是否都具有相等的散列码”。要编写单元测试来验证你的推断。如果相等的实例有着不相等的散列码,则要找出原因,并修正。
网友评论