今天来看提示十一: 覆盖 equals 方法时总要覆盖hashCode 方法。
如果不这样做,你的类违反了 hashCode 的通用约定,这会阻止它在 HashMap 和 HashSet 这样的集合中正常工作。
-
程序执行期间,只要对象的
equals
方法的比较操作所用到的信息没有被修改,那么多次调用hashCode
方法都必须始终如一地返回同一个整数。 -
如果两个对象根据
equals
比较相等,那么hashCode
结果应该相同。 -
如果两个对象使用
equals
比较必相等,则hashcode
不一定要得到不一样的结果。但是如果结果不同可以调高散列表的性能。
hashcode的返回值是一个Int,所以它最大的覆盖范围也就2^32,需要我们合理安排计算函数,让值域能够尽可能离散,提高相关性能。书中举了一个反例那就是不管什么情况,hashCode都返回42,这样也能满足上面的3点要求,但是会导致所有对象的哈希值都一样,哈希表退化为链表会退化成链表。
书中还给出了一种简单合理的hashCode实现方法:
-
如果是基本数据类型,则可以调用对用类型的
Type.hashCode(f)
方法。 -
如果是一个对象引用,则应该调用该对象的
hashcode
方法。 -
如果是一个数组,则需要把每一个元素单独处理。如果有些元素重要,有些不重要,可以只挑出来重要的元素。
-
最后把所有元素的hash结果按照公式加总:
resuot = 31 * result + c
。
之所以用31去乘,一是他是个奇素数,另一个是jvm会用移位和减法来获取更好的性能 31 * i ==(i << 5) - i
。另外书中也介绍了其他两种比较常见的计算方法:
-
com.google.common.hash.Hashing
,这个是google的Guava提供的方法,它尽量保证了不会出现哈希冲突。 -
Objects.hashcode
,它是JDK官方 提供的hash计算方法。缺点是因为它要创建数组,在速度上会慢一些。
我也看过org.apache.commons.lang.builder.HashCodeBuilder
apach提供的方法,也是一种不错的实现,是把重要的属性都放进去的builder模式,方便快捷。
另外如果一个类是不可变的,并且计算哈希码的代价很大,那么可以考虑在对象中缓存哈希码,而不是在每次请求时重新计算哈希码。String类里面就是这样做的。但是书中也说了String类和Integer将 hashCode 方法返回的确切值指定为实例值的函数,这样不是一种好的做法,妨碍了在未来版本中改进哈希函数的能力。
网友评论