hashcode方法返回对象的哈希码值。
hashcode的存在主要用于查找的快捷性,如Hashtable,HashMap等,hashcode是用来在散列存储结构中确定对象的存储地址的。
【Object.hashcode通用约定】
- 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有改变,那么对于这同一个对象调用多次,hashcode方法都必须始终如一地返回同一个整数。
- 如果两个对象根据equals(object)方法比较是相等的,那么调用这两个对象中的任意一个对象的hashcode方法都必须产生同样的整数结果。
- 如果两个对象根据equals(object)方法比较是不相等的,那么调用这两个对象中的任意一个对象的hashcode方法,则不一定要产生不同的整数结果。
【hashcode相等与对象相等之间的关系】-- 保证规范设计的前提下
- 如果两个对象相同,那么两个对象的hashcode也必须相同。
- 如果两个对象的hashcode相同,并不一定表示两个对象就相同,也就是不一定适合equals方法,只能够说明两个对象在散列表存储结构中,“存放在同一个篮子里”。
【为什么覆盖equals方法时总要覆盖hashcode方法?】
例子:我们创建两个相同值的对象,然后将p1对象作为key,"jenny"插入到hashmap中,再将p2对象作为key,获取对应的value值
public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
//覆盖equals方法
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof PhoneNumber))
return false;
//必须满足如下条件,才能说明为同一个对象
PhoneNumber pn = (PhoneNumber) obj;
return pn.areaCode == areaCode && pn.prefix == prefix && pn.lineNumber == lineNumber;
}
public static void main(String[] args){
Map<PhoneNumber, String> m = new HashMap<>();
//创建两个相同的对象
PhoneNumber p1 = new PhoneNumber(707, 867, 5309);
PhoneNumber p2 = new PhoneNumber(707, 867, 5309);
//添加到hashmap中
m.put(p1, "Jenny");
//比较对象p1和p2
System.out.println("p1.equals(p2): " + p1.equals(p2));
System.out.println("p2.equals(p1): " + p2.equals(p1));
//从hashmap中去获取对象p1和p2
System.out.println("get p1 from hashmap: " + m.get(p1));
System.out.println("get p2 from hashmap: " + m.get(p2));
}
}
输出结果:
p1.equals(p2): true
p2.equals(p1): true
get p1 from hashmap: Jenny
get p2 from hashmap: null
前两个结果说明p1和p2为同一对象,但我们发现以p1作为key时,是可以获取到对应的value,而以p2作为key时,却获取到null。
如果不覆盖equals方法的话,相等的对象可能返回的不相同的hash code。
【如何在覆盖equals方法时覆盖hashcode方法】
只要我们重写hashcode方法,返回一个适当的hash code即可。
@Override
public int hashCode() {
return 42;
}
这样的确能解决上面的问题,但实际上,这么做,会导致很差的性能,因为它总是确保每个对象都具有同样的散列码。因此,每个对象都被映射到同一个散列桶中,使得散列表退化成链表。
一个好的散列函数通常倾向于“为不相等的对象产生不同的散列码”。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。但实际上,要达到这种理想的情形是非常困难的。
【如何设置一个好的散列函数】
- 1、把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。
- 2、对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤:
a. 为对象计算int类型的散列码c:
- 对于boolean类型,计算(f?1:0)
- 对于byte,char,short,int类型,则计算(int)f
- 对于long类型,计算(int)(f^(f>>>32))
- 对于float类型,计算Float.floatToIntBits(f)
- 对于Double类型,计算Double.doubleToLongBits(f),然后再按照long类型处理
- 对于对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashcode
- 对于数组,则把每一个元素当作单独的域来处理。
b. 将获取到的c合并:result = 31 * reuslt + c;
- 3、返回result。
比如,我们可以优化上面的hashcode方法:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}