如何某类覆盖了equals方法而没有覆盖hashCode方法,则该类无法和基于散列的集合正常运作,比如HashMap、HashSet、HashTable集合。
举个例子:
public class Main {
static class PhoneNumber{ //定义一个电话号码类
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber)
{
this.areaCode=areaCode;
this.prefix=prefix;
this.lineNumber=lineNumber;
}
@Override
public boolean equals(Object obj) { //覆盖equals方法
if(obj.getClass()!=getClass())
{
return false;
}
PhoneNumber phoneNumber=(PhoneNumber)obj;
return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
}
}
public static void main(String[] args) {
Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));
}
}
运行结果:
null
我们设想的结果是 Sean,但是因为覆盖了equals方法而没有覆盖hashCode方法,就出现了异常,现在先用一个及其简单的办法覆盖一下hashCode方法看看能不能得到正常结果:
public class Main {
static class PhoneNumber{
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber)
{
this.areaCode=areaCode;
this.prefix=prefix;
this.lineNumber=lineNumber;
}
@Override
public boolean equals(Object obj) {
if(obj.getClass()!=getClass())
{
return false;
}
PhoneNumber phoneNumber=(PhoneNumber)obj;
return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
}
@Override
public int hashCode() { //一个极其简单的覆盖hashCode方法
return 23;
}
}
public static void main(String[] args) {
Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));
}
}
运行结果:
Sean
这就得到了我们预期的结果了,为何hashCode被如此覆盖就能正常工作呢?这就要从这些个Hash集合的工作原理说起了:
这些Hash集合,在添加,查找的时候都需要先看下集合里有没有该元素,如何查找呢?如果一个个用equals比较那效率就太低了,也不是Hash集合的作风,既然是Hash集合自然要用哈希函数对每个元素用hashCode方法生成一个哈希值,根据哈希值来存储到特定的存储空间,如果产生重复哈希值就可以利用拉链法这样的办法解决,这样所有元素散列存储在内存空间里。添加的过程就为先用hashCode方法对元素产生哈希值,然后根据这个哈希值找到存储空间,对该段存储空间的元素再用equals进行比对,如果有相同的元素则不添加,没有则添加。查找过程类似。这样的哈希过程就大大提高了效率。
根据这个原理,所以刚才的极简的hashCode覆盖方法就等于把所以元素的哈希值都定位同一个常数,把所有元素线性存储在这个空间里,虽然这个办法可以暂时解决问题但是它是十分恶劣的办法,当元素数量大大增加时效率会极其低。
那么为什么原来hashCode办法会无效呢?看看这个原来的hashCode方法:
public native int hashCode();
这个就是原来继承Object的hashCode方法,这个方法是一个外部方法,功能是返回元素在内存空间中的地址,则这个hashCode方法刚好配合Object的equals方法使用:
public boolean equals(Object obj) {
return (this == obj);
}
Object的equals方法正好是比较元素地址的,这与hashCode返回元素地址是一致的。所以这告诉我们覆盖hashCode方法的时候里面涉及的参数要和覆盖equals方法里面依赖的参数一致,尽量包含到元素类所有特征性变量。根据这个准则我们再修改一下例子中的hashCode方法:
public class Main {
static class PhoneNumber{
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber)
{
this.areaCode=areaCode;
this.prefix=prefix;
this.lineNumber=lineNumber;
}
@Override
public boolean equals(Object obj) {
if(obj.getClass()!=getClass())
{
return false;
}
PhoneNumber phoneNumber=(PhoneNumber)obj;
return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
}
@Override
public int hashCode() { //改良版的hashCode覆盖方法
int result;
result=areaCode*100+prefix*10+lineNumber;
return result;
}
}
public static void main(String[] args) {
Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));
}
}
运行结果:
Sean
结果没有问题,但是这个hashCode方法可能也不是最好的,设计这个方法是一门学问,不同的hashCode方法会构成不同的散列方式,有好有坏 。