改写equals时总是要改写hashCode

18 篇文章 0 订阅
15 篇文章 0 订阅

 

java.lnag.Object中对hashCode的约定:
  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

 

看个不改写hashCode导致使用hashMap不能出现预期结果的例子:
public final class PhoneNumber{
private final short areaCode;
private final short exchange;
private final short extension;

public PhoneNumber(int areaCode,int exchage,int extension){
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;
}
private static void rangeCheck(int arg,int max, String name){
if(arg<0>max) throw new IllegalArgumentException(name+":"+arg);
}
public boolean equals(Object o){
if (o == this) reutrn true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn=(PhoneNumber)o;
return pn.extension==extension && pn.exchange=exchange && pn.areaCode=areaCode;
}
//No hashCode method
...
}
现在有以下几行程序:
Map m=new HashMap();
m.put(new PhoneNumber(1,2,3),"Jenny");
则m.get(new PhoneNumber(1,2,3))的返回值什么?
  虽然这个实例据equals是相等的,但由于没改写hashCode而致两个实例的散列码并不同(即违反第二条要求),因则返回的结果是null而不是"Jenny".
  理想情况下,一个散列函数应该把一个集合中不相等的实例均匀地分布到所有可能的散列值上,下面是接近理想的“处方”:

  1. 把某个非零常数值(如17)保存在一个叫result的int类型的变量中;
  2. 对于对象中每个关键字域f(指equals方法中考虑的每一个域),完成以下步骤:
    1. 为该域计算int类型的散列码c:
      1. 如果该域是bloolean类型,则计算(f?0:1)
      2. 如果该域是byte,char,short或int类型,则计算(int)f
      3. 如果该域是long类型,则计算(int)(f^(>>>32))
      4. 如果该域是float类型,则计算Float.floatToIntBits(f)
      5. 如果该域是double类型,则计算Double.doubleToLongBits(f)得一long类型值,然后按前述计算此long类型的散列值
      6. 如果该域是一个对象引用,则利用此对象的hashCode,如果域的值为null,则返回0
      7. 如果该域是一个数组,则对每一个数组元素当作单独的域来处理,然后安下一步的方案来进行合成


    2. 利用下面的公式将散列码c 组合到result中。result=37*result+c;

  3. 检查“相等的实例是否具有相等的散列码?”,如果为否,则修正错误。

  依照这个处方,得PhoneNumber的hashCode方法:
public int hashCode(){
int result=17;
result=37*result+areaCode;
result=37*result+exchange;
result=37*result+extension;
return result;
}
  如果计算散列码的代价比较高,可以考虑用内部保存这个码,在创建是生成或迟缓初始化生成它。不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。

style="MARGIN-TOP: 0px; FLOAT: left" border="0" marginwidth="0" framespacing="0" marginheight="0" src="http://wz.csdn.net/vote.aspx?t=equals%28%29%20%u548C%20hashCode%28%29%20-%20Yonghua%27s%20Tech%20Space%20-%20CSDNBlog&u=http%3A//blog.csdn.net/dr2tr/archive/2007/02/28/1516722.aspx" frameborder="0" noresize="noresize" width="54" scrolling="no" height="75"> equals() 和 hashCode()   <script src="http://blog.csdn.net/count.aspx?ID=1516722&Type=Rank" type="text/javascript"></script>   CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

 我们知道,equals()函数是用来做比较的。java中的比较有两种:一种是内存地址的比较,一种是内容的比较。而比较个体也有两种:一种是简单类型(这类简单说来无所谓内存地址的比较或者内容比较的区别);还有一种是对象的比较,本文中说的主要是后者

在java中,(对象)内存地址的比较,是通过==完成的。比如 

if (obj1  ==  obj2) {
    ...
}

这样的语句中,我们认为,如果obj1和obj2的内存地址相同,则返回true

而equals()通常是比较内容的。这里说“通常” ,是因为在最根本的Object类中,equal()函数做的是地址的比较。而在其他几乎所有的类中,equals()都经过重载,进行内容的比较。

而在说equals()的时候我们还涉及hashCode()是因为在有些应用中(比如,HashMap的key是对象),必须在重载equals()的同时重载hashCode()。因为java中默认(Object)的hashCode是根据对象的地址计算得到的。

我们通常不会注意到这个问题,因为我们通常所使用的key都是简单类型,或者是String, Long等一些特殊的对象(其特殊性请参看笔者在写java 浅拷贝和深拷贝时的讨论),这时候,这个问题被我们无意间绕过了

有人已经概括了这种我们忽略了的情况:“如果你想将一个对象A放入另一个收集(集合)对象B里,或者使用这个对象A为查找一个元对象在收集对 象B里位置的钥匙(key),并支持是否容纳(isContains()),删除收集对象B里的元对象(remove()?)这样的操作,那么,equals()和hashCode()函数必须开发者自己定义。” (括号为笔者添加)

为了便于理解,举一段程序为例:

import  java.util. * ;
public   class  Person {

int id;
String name;

//define getter and setter here, omited

public Person(int id, String name){
     
this.id = id;
     
this.name = name;
     
}


public boolean equals(Object o){
       
if(this==o) return true;
    
if(o instanceof Person)
    
return (this.id == ((Person)o).id) && (this.name.equals(((Person)o).name));
    }


/*         
public int hashCode(){
    return id*37;
}
*/


public static void main(String args[]) {
    Person p1 
= new Person(1,"aaa");
    Person p2 
= new Person(1,"aaa");
    Map map 
= new HashMap();

    map.put(p2,p1);
    Person value 
= (Person)map.get(p1);
    System.out.println(value.name);
  }

}

这段代码的结果是什么?答案是nullPointerExcetpion.

而把hashCode()的注释去除,程序就可以返回正确的结果了。为什么呢?因为:

Map.put(key,value)时根据key.hashCode生成一个内部hash值,根据这个hash值将对象存放在一个table中

Map.get(key)会比较key.hashCode和equals方法,当且仅当这两者相等时,才能正确定位到table。而我们说过,默认的java是对地址进行比较的。

 

参考:

http://blog.csdn.net/RichardSundusky/archive/2007/02/12/1508028.aspx

http://blog.csdn.net/pbnow/archive/2006/04/25/677253.aspx

http://blog.csdn.net/jery_lee/archive/2004/10/13/135484.aspx

http://blog.csdn.net/ngqzmjmj/archive/2005/04/27/365149.aspx



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1516722

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值