第9条:覆盖equals时总要覆盖hashCode
在每一个覆盖equals方法的类中,都必须覆盖hashCode方法,如果不这么做,会导致这个类无法结合所有基于散列的集合的正常使用,如HashMap,HashSet,Hashtable。
Object规范中有这么一条:
如果两个对象根据equals(Object)方法比较是相等的,那么调用这对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
因为对于两个对象A和B,如果A.equals(B)为true,则代表A与B在逻辑上是相等的,但是Object中的hashcode方法,则是根据对象的内存地址来生成hashcode,这也就会导致两个逻辑上相等的对象的hashcode却是不同的。这在使用基于hashcode的散列表(如HashMap)时会出现问题:
public class PhoneNumber{
private int areaCode;
private int prefix;
private int lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber){
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
@Override
public boolean equals(Object o){
if(o == this){
return this;
}
if(!(o instanceof PhoneNumber)){
return false;
}
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
}
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
m.get(new PhoneNumber(707, 867, 5309)); //return null
因为在HashMap中,会先根据传入对象的hashcode找到相应的桶,然后再在桶中寻找相等的对象,换言之如果连两个对象的hashcode都不同,那么也不会去比较这两个对象是否相等。
一个好的hashcode方法将会为不同的对象产生不同的hashcode,在最理想的情况下,应该把集合中所有不相等的对象均匀分布到所有可能的值上。
在hashcode的计算过程中,可以将冗余域(即可以通过参与计算的其它值计算出来的值)排除在外,还有必须排除equals方法中没有的所有值,否则有可能出现两个对象逻辑相等但是生成的不同的状况。
如果一个类中参与hashcode计算的变量都是不可变(final)的,而且hashcode计算的开销又非常大,那么可以在创建这个对象,或第一次使用hashcode时计算这个值,然后将hashcode保存在这个对象内部。