以前只是知道hashcode是用来计算散列值的,用来存储在hashmap中使用。今天在看ibatis.net的源码,突然想起来上次遇到的问题。
我建立了一些wrapper类型用来包裹.net的基本类型,来解决数据库中查找出来空类型的问题。
当我把一个wrapper类型的对象放到hashtable中时。虽然改写了Equals方法,但是仍然无法利用另外一个wrapper类型的但是wrapper的value一样的对象去索引它。
今天一下想起来可能是没有重写GetHashCode的方法,google一搜索,果然如此。转载一下竹笋炒肉的一篇笔记如下。虽然是java的,但是同样适用.net。
读《Effective java 中文版》(9)
第8条:改写equals时总是要改写hashCode
java.lnag.Object中对hashCode的约定:
- 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
- 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
- 如果两个对象根据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 || arg>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".
理想情况下,一个散列函数应该把一个集合中不相等的实例均匀地分布到所有可能的散列值上,下面是接近理想的“处方”:
- 把某个非零常数值(如17)保存在一个叫result的int类型的变量中;
- 对于对象中每个关键字域f(指equals方法中考虑的每一个域),完成以下步骤:
- 为该域计算int类型的散列码c:
- 如果该域是bloolean类型,则计算(f?0:1)
- 如果该域是byte,char,short或int类型,则计算(int)f
- 如果该域是long类型,则计算(int)(f^(>>>32))
- 如果该域是float类型,则计算Float.floatToIntBits(f)
- 如果该域是double类型,则计算Double.doubleToLongBits(f)得一long类型值,然后按前述计算此long类型的散列值
- 如果该域是一个对象引用,则利用此对象的hashCode,如果域的值为null,则返回0
- 如果该域是一个数组,则对每一个数组元素当作单独的域来处理,然后安下一步的方案来进行合成
- 如果该域是bloolean类型,则计算(f?0:1)
- 利用下面的公式将散列码c 组合到result中。result=37*result+c;
- 为该域计算int类型的散列码c:
- 检查“相等的实例是否具有相等的散列码?”,如果为否,则修正错误。
依照这个处方,得PhoneNumber的hashCode方法:
public int hashCode(){
int result=17;
result=37*result+areaCode;
result=37*result+exchange;
result=37*result+extension;
return result;
}
如果计算散列码的代价比较高,可以考虑用内部保存这个码,在创建是生成或迟缓初始化生成它。不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。