考虑一个天气预报系统,将Groundhog与Prediction对象联系起来。创建竖如这两个类,使用Groundhog作为键,Prediction作为值。以下是来自书上的代码。
Java代码
//: containers/Groundhog.java
// Looks plausible, but doesn't work as a HashMap key.
public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
public String toString() {
return "Groundhog #" + number;
}
} ///:~
//余早启: containers/Prediction.java
// Predicting the weather with groundhogs.
import java.util.*;
public class Prediction {
private static Random rand = new Random(47);
private boolean shadow = rand.nextDouble() > 0.5;
public String toString() {
if(shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
} ///:~
//: containers/SpringDetector.java
// What will the weather be?
import java.lang.reflect.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class SpringDetector {
// Uses a Groundhog or class derived from Groundhog:
public static
void detectSpring(Class
Constructor
Map
new HashMap
for(int i = 0; i < 10; i++)
map.put(ghog.newInstance(i), new Prediction());
print("map = " + map);
Groundhog gh = ghog.newInstance(3);
print("Looking up prediction for " + gh);
if(map.containsKey(gh))
睁岁print(map.get(gh));
else
print("Key not found: " + gh);
}
public static void main(String[] args) throws Exception {
detectSpring(Groundhog.class);
}
} /* Output:
map = {Groundhog #3=Early Spring!, Groundhog #7=Early Spring!, Groundhog #5=Early Spring!, Groundhog #9=Six more weeks of Winter!, Groundhog #8=Six more weeks of Winter!, Groundhog #0=Six more weeks of Winter!, Groundhog #6=Early Spring!, Groundhog #4=Six more weeks of Winter!, Groundhog #1=Six more weeks of Winter!, Groundhog #2=Early Spring!}
Looking up prediction for Groundhog #3
Key not found: Groundhog #3
*///:~
这段代码首先使用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)方法。
Java代码
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry
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()方法了