很多人可能都知道,在每个覆盖了equals
方法的类中,也必须覆盖hashCode
方法;但是这里面的原因是什么呢?
我想应该从两个方面阐述这个问题:
- 什么情况下需要覆盖
equals
? - 在覆盖了
equals
的同时未覆盖hashCode
会导致什么问题?
第一个问题,什么情况下需要覆盖equals
呢?首先我们需要知道,如果不覆盖equals
,由于每个类的实例在内存中都是唯一的,那么就无法做到两个业务上等同的实例equals
也返回true
,所以说覆盖equals
一般都是业务需要,即有些场景下只需要对象的关键属性相等,就认为他们相等
其次,如果在覆盖了equals
的同时未覆盖hashCode
会导致什么问题呢?这个问题的答案在于:如果没有遵守覆盖equals
时同时覆盖hashCode
,就会违反Object.hashCode
的通用约定(具体的约定大家可以至Obejct
类hashCode
方法注释处查看),从而导致该类无法结合所有基于散列的集合一起正常工作,这样的集合包括HashMap
,HashSet
和HashTable
,有点难理解是吗?没关系,举例说明就清楚了,假设有一个PhoneNumber
类,只需两个实例的prefix
和lineNumber
属性相等,即可认为两个PhoneNumber
相等:
public class PhoneNumber {
private short areaCode;
private short prefix;//关键属性
private short lineNumber;//关键属性
public PhoneNumber(short areaCode, short prefix, short lineNumber) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
public boolean equals(Object o) {
if(o == this) {
return true;
}
if(!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber compareO = (PhoneNumber)o;
return compareO.prefix == this.prefix
&& compareO.lineNumber == this.lineNumber;
}
}
然后将PhoneNumber
与HashMap
一起使用:
Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(21, 37, 3245), "xiaobai");
System.out.println(m.get(new PhoneNumber(53, 37, 3245)));//输出:null
你可能期望输出的是:"xiaobai",因为put
和get
的两个key对象的equals
返回是true,但实际上得到的却是null
,为什么呢?原因就在于未覆盖hashCode
,两个对象即便相等,但是其hashCode
还是可能不等,那么put
方法把PhoneNumber
对象存放至一个散列桶(hash bucket)中,而get
方法却在另外一个散列桶中查找这个对象,当然找不到,退一步说,即便是两个实例刚好被放至于同一个散列桶中,get
方法依然还是会返回null
,因为HashMap有一项机制:如果两个实例散列码不匹配, 直接放弃比较其等同性
我想,讲到这里大家应该清楚了为什么覆盖了equals方法的同时必须覆盖hashCode的原因所在,但有人可能会说,我这个对象不会和基于散列的集合一起使用的,所以不需要遵守这个约定,但正所谓魔鬼隐藏在细节之中,你现在不会使用不代表以后不会使用(不要相信自己的记忆);你知道不能一起使用,不代表别人知道(即便有注释别人也不一定会看)。所以建议一般情况下都遵守这项约定,只是稍微增加一点工作量而已,却是代码健壮的基石