为什么要同时实现hashCode和equals

2024-12-03 20:41:15
推荐回答(1个)
回答1:

  考虑一个天气预报系统,将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 type) throws Exception {
  Constructor ghog = type.getConstructor(int.class);
  Map 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 = 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()方法了