代码改变世界

Item 9 覆盖equals时总要覆盖hashCode

2015-03-24 23:09  ttylinux  阅读(218)  评论(0编辑  收藏  举报

为什么覆盖equals时,总要覆盖hashCode?

 
原因是,根据Object规范:
如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
如果违反这个规定,那么,在使用应用了散列码的集合(HashMap,HashSet,Hashtable)时,就会出现问题。
 
也就是,两个相等的对象,必须要有相等的散列码(hashCode)。两个对象,进行比较的时候,使用的关键域是一样的,然后,使用这些关键域作为参数来生成散列码。两个对象相应的关键域的值相等,那么,使用相等的关键域,使用同样的生成方法,那么,生成出来的散列码是相等的
 
 
两个对象相等,那么,它们的hashCode是相等的么?
答:是的。根据Object规范,这是必须满足的。
 
两个对象的hashCode相等,那么这两个对象是相等的么?
答:不一定。因为,equals的实现,有可能是使用其它方式,未必只是简单地比较hashCode方法中用到的关键域。所以,不能根据对象的hashCode来判定两个对象是否相等。
 
两个对象根据equals(Object)方法进行比较,它们不相等,那么,这两个对象的hasoCde一定是不同的么?
答:不一定。同样的道理,因为equals方法的实现,未必只是简单地比较hashCode方法中用到的关键域的值。
 
hashCode的生成方式:
 
使用---HashCodeBuilder,该类是参考<<effective java>>中描述的规则实现的。
 
例子:
import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
  private final short areaCode ;
  private final short prefix ;
  private final short lineNumber ;

  public PhoneNumber( int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix" );
    rangeCheck(lineNumber, 9999, "line number");
    this. areaCode = ( short) areaCode;
    this. prefix = ( short) prefix;
    this. lineNumber = (short ) lineNumber;
  }

  private static void rangeCheck(int arg, int max, String name) {
    if (arg < 0 || arg > max)
      throw new IllegalArgumentException(name + ": " + arg);
  }

  @Override
  public boolean equals(Object o) {
    if (o == this)
      return true;
    if (!(o instanceof PhoneNumber))
      return false;
    PhoneNumber pn = (PhoneNumber) o;
    return pn. lineNumber == lineNumber && pn.prefix == prefix && pn. areaCode == areaCode;
  }

  // Broken - no hashCode method!

  // A decent hashCode method - Page 48
  // @Override public int hashCode() {
  // int result = 17;
  // result = 31 * result + areaCode;
  // result = 31 * result + prefix;
  // result = 31 * result + lineNumber;
  // return result;
  // }

  // Lazily initialized, cached hashCode - Page 49
  // private volatile int hashCode; // (See Item 71)
  //
  // @Override public int hashCode() {
  // int result = hashCode;
  // if (result == 0) {
  // result = 17;
  // result = 31 * result + areaCode;
  // result = 31 * result + prefix;
  // result = 31 * result + lineNumber;
  // hashCode = result;
  // }
  // return result;
  // }

  public static void main(String[] args) {
    Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
    m.put (new PhoneNumber(707, 867, 5309), "Jenny");
    System.out.println(m.get( new PhoneNumber(707, 867, 5309)));
  }
}
---------------------------------------------
输出结果是:
null
 
原因:在覆盖equals方法的时候,没有覆盖hashCode方法。PhoneNumber作为HashMap的K,在main方法中,生成了两个PhoneNumber实例,这两个PhoneNumber实例通过equals方法比较是相等的。但是,PhoneNumber,并没有覆盖hashCode方法,所以,这两个实例具备不同的hashCode。现在要做的就是,覆盖hashCode方法,然后,使通过equals比较相等的两个PhoneNumber实例返回相等的hashCode。
 
HashCode生成方式,使用HashCodeBuilder:
下面的代码添加了一个覆盖的hashCode方法,输出结果,就找到了Jenny。
public final class PhoneNumber {
  private final short areaCode ;
  private final short prefix ;
  private final short lineNumber ;

  public PhoneNumber( int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix" );
    rangeCheck(lineNumber, 9999, "line number");
    this. areaCode = ( short) areaCode;
    this. prefix = ( short) prefix;
    this. lineNumber = (short ) lineNumber;
  }

  private static void rangeCheck(int arg, int max, String name) {
    if (arg < 0 || arg > max)
      throw new IllegalArgumentException(name + ": " + arg);
  }

  @Override
  public boolean equals(Object o) {
    if (o == this)
      return true;
    if (!(o instanceof PhoneNumber))
      return false;
    PhoneNumber pn = (PhoneNumber) o;
    return pn. lineNumber == lineNumber && pn.prefix == prefix && pn. areaCode == areaCode;
  }

  @Override
  public int hashCode() {
    return new HashCodeBuilder(17, 37).append(lineNumber).append(prefix ).append(areaCode)
        .toHashCode();
  }

  // Broken - no hashCode method!

  // A decent hashCode method - Page 48
  // @Override public int hashCode() {
  // int result = 17;
  // result = 31 * result + areaCode;
  // result = 31 * result + prefix;
  // result = 31 * result + lineNumber;
  // return result;
  // }

  // Lazily initialized, cached hashCode - Page 49
  // private volatile int hashCode; // (See Item 71)
  //
  // @Override public int hashCode() {
  // int result = hashCode;
  // if (result == 0) {
  // result = 17;
  // result = 31 * result + areaCode;
  // result = 31 * result + prefix;
  // result = 31 * result + lineNumber;
  // hashCode = result;
  // }
  // return result;
  // }

  public static void main(String[] args) {
    Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
    m.put(new PhoneNumber(707, 867, 5309), "Jenny");
    System.out.println(m.get( new PhoneNumber(707, 867, 5309)));
  }
}

确定关键域,关键域指的是覆盖的equals方法中涉及到的每个域;然后,使用这些关键域,作为参数,用它们生成一个hashCode。这个可以通过HashCodeBuilder来实现,具体参照上述代码。