当用到Java中的集合时,会涉及到HashSet和HashMap,这里拿HashSet举例来说明关于hash的问题。
当我们向HashSet集合中存入一个元素时,HashSet会调用该对象的的hashCode()方法来得到对象的hashCode值,然后根据hashCode值来决定对象的存放位置。如果两个对象使用equals方法比较结果为true,而hashCode值不相同,HashSet依然会接受这两个值,存放在集合中的不同位置。
这也就意味着,如果想要在HashSet中不存在相同的元素,那么如果两个对象通过equals方法比较结果相等,那么两个对象的hashCode值也应该相等。
如果两个对象的hashCode值相同,但是equals方法比较结果不相同,那么更加糟糕。HashSet试图将他们保存在同一个位置,但一个位置又只能保存一个,所以实际上会在这个位置使用链式结构来保存多个对象。因为HashSet是通过hashCode来定位元素的,这样的结构会导致性能下降。
当我们想要重写对象的equals方法时,也因该重写hashCode()方法,以保证他们equals比较得到true时,hashCode值也相同。
重写hash Code方法的基本规则:
1:在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的hashCode值。
2:当两个对象通过equals方法比较结果为true时,来给你个对象的hashCode方法返回的值也应该相同。
3:对象中用作equals方法比较标准的实例变量,都应该用于计算hashCode值。
重写HashCode()方法的一般步骤:
1:把对象的每个有意义的实例变量(即每个参于equals()方法比较的实例变量)计算出一个int类型的hashCode值。计算方式如下:
实例变量类型:boolean ------->计算方式:hashCode = (f?0:1);
实例变量类型:整数类型(int、byte、short、char) ------->计算方式:hashCode = (int)f;
实例变量类型:long ------->计算方式:hashCode = (int)(f^(f>>>32));
实例变量类型:float ------->计算方式:hashCode = Float.floatToIntBits(f);
实例变量类型:double ------->计算方式:Long l = Double.doubleToIntBits(f); hashCode = (int)(l^(l>>>32));
实例变量类型:引用类型 ------->计算方式:hashCode = f.hashCode(f);
2:用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回。
如:return f1.hashCode() +(int)f2;
为了避免直接相加产生偶然相等,可以通过为各实例变量的hashCode值乘上一个质数后相加。
如:return f1.hashCode() * 19 + (int)f2 * 31;
PS:当程序把可变对象添加到HashSet中后,尽量不要去修改该集合中怨怒是中参与计算hashCode、equals的实例变量,否则有可能导致Hashset无法正确操作这些元素。例:
import java.util.HashSet; import java.util.Iterator; class R{ int count; public R(int count){ this.count = count; } public String toString(){ return "R[count:"+count+"]"; } public boolean equals(Object obj){ if(this == obj) return true; if(obj != null && obj.getClass() == R.class){ R r = (R)obj; return this.count == r.count; } return false; } public int hashCode(){ return this.count; } } public class HashSetTest2{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new R(5)); hs.add(new R(-3)); hs.add(new R(9)); hs.add(new R(-2)); //打印HashSet集合,集合元素没有重复 System.out.println(hs); //取出第一个元素 Iterator it = hs.iterator(); R first = (R)it.next(); //为第一个元素的count实力变量赋值 first.count = -3; //再次输出HashSet集合,集合元素有重复元素 System.out.println(hs); //删除count为-3的R对象 hs.remove(new R(-3)); //删除的是原本定义为-3的对象,而不是后面改动的对象。因为HashSet根据hashCode值定位。 System.out.println(hs); System.out.println("hs是否包含count为-3的对象?"+hs.contains(new R(-3))); //结果为false,因为被删除了 System.out.println("hs是否包含count为-2的R对象?"+hs.contains(new R(-2))); //结果为false,因为值被改动了 } }