
很多人可能都知道,在每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法;但是這里面的原因是什么呢?
我想應(yīng)該從兩個方面闡述這個問題:
- 什么情況下需要覆蓋
equals? - 在覆蓋了
equals的同時未覆蓋hashCode會導(dǎo)致什么問題?
第一個問題,什么情況下需要覆蓋equals呢?首先我們需要知道,如果不覆蓋equals,由于每個類的實(shí)例在內(nèi)存中都是唯一的,那么就無法做到兩個業(yè)務(wù)上等同的實(shí)例equals也返回true,所以說覆蓋equals一般都是業(yè)務(wù)需要,即有些場景下只需要對象的關(guān)鍵屬性相等,就認(rèn)為他們相等
其次,如果在覆蓋了equals的同時未覆蓋hashCode會導(dǎo)致什么問題呢?這個問題的答案在于:如果沒有遵守覆蓋equals時同時覆蓋hashCode,就會違反Object.hashCode的通用約定(具體的約定大家可以至Obejct類hashCode方法注釋處查看),從而導(dǎo)致該類無法結(jié)合所有基于散列的集合一起正常工作,這樣的集合包括HashMap,HashSet和HashTable,有點(diǎn)難理解是嗎?沒關(guān)系,舉例說明就清楚了,假設(shè)有一個PhoneNumber類,只需兩個實(shí)例的prefix和lineNumber屬性相等,即可認(rèn)為兩個PhoneNumber相等:
public class PhoneNumber {
private short areaCode;
private short prefix;//關(guān)鍵屬性
private short lineNumber;//關(guān)鍵屬性
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",因?yàn)?code>put和get的兩個key對象的equals返回是true,但實(shí)際上得到的卻是null,為什么呢?原因就在于未覆蓋hashCode,兩個對象即便相等,但是其hashCode還是可能不等,那么put方法把PhoneNumber對象存放至一個散列桶(hash bucket)中,而get方法卻在另外一個散列桶中查找這個對象,當(dāng)然找不到,退一步說,即便是兩個實(shí)例剛好被放至于同一個散列桶中,get方法依然還是會返回null,因?yàn)镠ashMap有一項(xiàng)機(jī)制:如果兩個實(shí)例散列碼不匹配, 直接放棄比較其等同性
我想,講到這里大家應(yīng)該清楚了為什么覆蓋了equals方法的同時必須覆蓋hashCode的原因所在,但有人可能會說,我這個對象不會和基于散列的集合一起使用的,所以不需要遵守這個約定,但正所謂魔鬼隱藏在細(xì)節(jié)之中,你現(xiàn)在不會使用不代表以后不會使用(不要相信自己的記憶);你知道不能一起使用,不代表別人知道(即便有注釋別人也不一定會看)。所以建議一般情況下都遵守這項(xiàng)約定,只是稍微增加一點(diǎn)工作量而已,卻是代碼健壯的基石