Effective Java——Item8:改写equals的时候总是要改写hashCode

引自 http://www.cnblogs.com/wxf0701/archive/2008/04/24/1169809.html

更多Effective Java 内容,参见 http://www.cnblogs.com/wxf0701/tag/java+/

/**

 *在改写equals的时候总是要改写hashCode,如果不这样的话,就会违反Object.hashCode的通用约定,

 *导致这个类无法与所有基于散列值的集合类结合在一起正常工作,包括HashMap,HashSetHashtable

 *hashCode的约定内容:

 *hashCode()返回该对象的哈希码值。支持该方法是为哈希表提供一些优点.

 *(1)Java应用程序执行期间,在同一对象上多次调用hashCode方法时,必须一致地返回相同的整数,

 *   前提是对象上equals比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的

 *   另一次执行,该整数无需保持一致。

 *(2)如果根据equals(Object)方法,两个对象是相等的,那么在两个对象中的每个对象上调用

 *   hashCode方法都必须生成相同的整数结果。(不改写hashCode违反的关键是本条)

 *(3)以下情况不是必需的:如果根据equals(java.lang.Object)方法,两个对象不相等,那么在两个对象

 *   中的任一对象上调用hashCode方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生

 *   成不同整数结果可以提高哈希表的性能。*

 */

publicclassPhoneNumber {

   privatefinalshortareaCode;

   privatefinalshortexchange;

   privatefinalshortextension;

 

   publicPhoneNumber(intareaCode,intexchange,

                      intextension) {

       rangeCheck(areaCode,  999,"area code");

       rangeCheck(exchange,  999,"exchange");

       rangeCheck(extension, 9999,"extension");

       this.areaCode = (short) areaCode;

       this.exchange = (short) exchange;

       this.extension= (short) extension;

   }

 

   privatestaticvoidrangeCheck(intarg,intmax,

                                  String name) {

       if(arg < 0 || arg > max)

          thrownewIllegalArgumentException(name +": "+ arg);

   }

 

   publicbooleanequals(Object o) {

       if(o ==this)

           returntrue;

       if(!(oinstanceofPhoneNumber))

           returnfalse;

       PhoneNumber pn = (PhoneNumber)o;

       returnpn.extension==extension&&

              pn.exchange ==exchange &&

              pn.areaCode ==areaCode;

   }

 

  /**如果没有改写hasCode方法,当将该类与HashMap结合使用时:

   *Mapm=newHashMap();

   *m.put(newPhoneNumber(408,867,"Jenny");

   *当你期望m.get(newPhoneNumber(408,867,"Jenny"))会返回"Jenny"时,它却返回null.

   *因为这里涉及到两个PhoneNumber实例,前者插入到HashMap中,后者与前者相等,用于检索。

   *由于没有改写hashCode,导致两个相等的实例具有不同的散列码,违反了hashCode约定。

   */

   

   /**Q:如何编写hashCode方法?

    *A:理想的散列函数应满足第3条约定——为不相等的对象产生不相等的散列码,

    *   把集合中不相等的实例均匀地分布到所有可能的散列值上。好的处方

    *   (1)把某个非零常数值,比如17,保存在一个变量里,比如:intresult=17;

    *   (2)对于对象中的每个关键域f,完成以下步骤

    *      a.如果该值是boolean类型,则计算(f?0:1)

    *      b.如果是byte,char,short或者int,则计算(int)f

    *      c.如果是long,则计算(int)(f^(f>>>32))

    *      d.如果是float,则计算Float.floatToIntBits(f)

    *      e.如果是double,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后(2).c

    *        long型值进行计算

    *      f.如果是一个对象引用,则同样递归调用对象的hashCode.

    *        如果要求一个更复杂的比较,则为该域计算一个规范表示(canonicalrepresentation)”,然后

    *        针对这个范式调用hashCode.

    *        如果该域为null,则返回0(或其它某个常数)

    *      g.如果是一个数组,则把每一个元素当作一个单独的域来处理

    *   (3)按公式计算散列码result=37*result+c

    *   (4)返回result

    *   (5)写完hashCode方法后,检查是否相等的实例具有相等的散列码    *

    */

/*

   //改写后的散列函数

   public int hashCode() {

       int result = 17;

       result = 37*result + areaCode;

       result = 37*result + exchange;

       result = 37*result + extension;

       return result;

   }

*/

 

/**

 *注意:(1)如果一个类是非可变的,并且计算散列码的代价比较大,那么应考虑把散列码缓存在对象内部,而不是每次

 *     请求时都重新计算;

 *     (2)如果你觉得这种类型的大多数对象都会被用做散列键(hashkeys),那么你应该在创建实例时就计算散列码。

 *     否则,采用延缓初始化散列码。

 *     (3)不要试图从散列码计算机中排除一个对象的关键部分以提高性能。

 */    

   

/*    Lazily initialized, cached hashCode

   private volatile int hashCode = 0; // (See Item 48)

 

   public int hashCode() {

       if (hashCode == 0) {

           int result = 17;

           result = 37*result + areaCode;

           result = 37*result + exchange;

           result = 37*result + extension;

           hashCode = result;

       }

       return hashCode;

   }

*/

 

   // ... // Remainder omitted

 

   publicstaticvoidmain(String[] args) {

       Map m =newHashMap();

       m.put(newPhoneNumber(408, 867, 5309),"Jenny");

       System.out.println(m.get(newPhoneNumber(408, 867, 5309)));

   }

}