Java学习之HashMap: 为什么要同时实现hashCode()和equals()
问题来自Java编程思想(第四版第17章)的一个例子。
?
考虑一个天气预报系统,将Groundhog与Prediction对象联系起来。创建这两个类,使用Groundhog作为键,Prediction作为值。以下是来自书上的代码。
?
?这段代码首先使用Groundhog和与之相关联的Prediction填充HashMap,然后打印此HashMap,以便可以观察它是否被填入了一些内容。 然后使用标识数字为3的Groundhog作为键,查找与之对应的Prediction。
?
这段代码不能正确工作。 它无法找到数字3这个键。 问题在于Groundhog自动继承自基类Object,所以这里使用Object的hashCode()方法生成散列码, 而它默认是使用对象的地址计算散列码。 因此, Groundhog(3)生成的第一个实例的散列码与由Groundhog(3)生成的第二个实例的散列码是不同的, 而我们使用后者查找,当然找不到。
?
可能你认为,只需要编写恰当的hashCode()方法覆盖版本即可。但是它仍然无法正常运行,除非你同时覆盖equals()方法,它也是Object的一部分。 HashMap使用equals()判断当前的键是否与表中存在的键相同。?
?
那么, 这里提出一个问题,为什么覆盖hashCode()方法的同时,还需要覆盖equals()方法
?
直接看HashMap的源码可以找到答案。主要是它的get(Object key)方法。
?
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }?可以看到get(Object key)方法的过程其实很简单,遍历HashMap的内部维护的Entry数组,找到匹配的目标Entry并返回其value即可。 匹配的标准是:
Entry的hash值是否与参数key的hash值相等。 (这要求我们必须正确实现作为key的对象的hashCode()方法)Entry的key是否与参数key为同一对象,或者Entry的key"等于"参数key。 (注意是否"等于", 是通过调用参数key的equals()方法来完成的, 这要求我们必须正确实现作为key的对象的equals()方法)
明白以上这两个关键点,就不难解释为什么为什么覆盖hashCode()方法的同时,还需要覆盖equals()方法了
?
