重寫equals的同時為啥要重寫hashCode?
HashMap引申的問題
HashMap在重寫equal一定要重寫hashCode
Effective java這本書中,存在這麼一句話,**在每個覆蓋了equals方法的類中,都必須覆蓋hashCode方法。**大部分人應該都知道這個規則,但為什麼要這樣做,以及不這麼做會出現這麼問題呢?接下來,將對以上問題做探討。
下面有一個重寫了equals方法的PhoneNum類:
class PhoneNum{
private final int a,b,c;
PhoneNum(int a, int b, int c) {
this .a = a;
this.b = b;
this.c = c;
}
public boolean equals(Object o){
if(o==this){
return true;
}
if(! (o instanceof PhoneNum) ){
return false;
}
PhoneNum pn=(PhoneNum) o;
return pn.a==a&&pn.b==b&& pn.c==c;
}
}
注意:此類並沒有重寫hashCode方法。
接下來,我把這個類的例項put進hashMap
class Test{
static HashMap hashMap=new HashMap();
public static void main(String[] args) {
hashMap.put(new PhoneNum(1,1,1),"obj1");
Object value=hashMap.get(new PhoneNum(1,1,1)); //獲取值
System. out.println(value);
}
}
猜一猜,程式會輸出什麼?為obj1嗎?
下面來看看輸出結果:
接下來我再在PhoneNum類中重寫hashCode方法:
class PhoneNum{
private final int a,b,c;
PhoneNum(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
public boolean equals(Object o){
if(o==this){
return true;
}
if(! (o instanceof PhoneNum) ){
return false;
}
PhoneNum pn=(PhoneNum) o;
return pn.a==a&&pn.b==b&&pn.c==c;
}
public int hashCode(){
return 42;
}
}
再執行Test中main方法,得到的輸出:
是不是想問一句為啥呢?hashCode有這麼強大嗎
首先,我們先看看hashCode到底是啥,有啥作用
我們先找找Object中的hashCode方法,是不是想問為啥要在Object中找,記住,Java中的所有類都繼承自Object,是不是又要問沒有用extends關鍵字,是怎麼繼承的,這個以後再說,先記住這點。
public native int hashCode();
oops!!原來是本地方法,而且實現體不是用java實現的
再來看看官方文件對hashCode的描述:
hashCode方法返回該物件的雜湊碼值。支援該方法是為雜湊表提供一些優點,例如,java.util.Hashtable 提供的雜湊表。
hashCode 的常規協定是:
在 Java 應用程式執行期間,在同一物件上多次呼叫 hashCode 方法時,必須一致地返回相同的整數,前提是物件上 equals 比較中所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。
如果根據 equals(Object) 方法,兩個物件是相等的,那麼在兩個物件中的每個物件上呼叫 hashCode 方法都必須生成相同的整數結果。
以下情況不 是必需的:如果根據 equals(java.lang.Object) 方法,兩個物件不相等,那麼在兩個物件中的任一物件上呼叫 hashCode 方法必定會生成不同的整數結果。但是,程式設計師應該知道,為不相等的物件生成不同整數結果可以提高雜湊表的效能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的物件返回不同的整數。(這一般是通過將該物件的內部地址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實現技巧。)
當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定宣告相等物件必須具有相等的雜湊碼。
那麼,物件的hashCode到底是怎麼來的呢
通過物件的內部地址(也就是實體地址)轉換成一個整數,然後該整數通過hash函式的演算法就得到了hashCode。那麼hashCode有什麼用呢?HashCode是用來在雜湊儲存結構中確定物件的儲存地址的。換句話說,hashCode是用來物件定位其在hash表中位置的。HashCode的作用主要是為了查詢的快捷性。比如,在查詢物件時,根據物件的hashCode就快速定位物件所在hash表的位置,但此時要注意一點,也就是hash衝突,hash衝突也就是不同物件的hashCode一樣,這時候在hash表中存放的位置也一樣,這時候就可以用連結串列的形式把這些hashCode相同的物件串起來,這也就是解決hash衝突最常用的方法——拉鍊法,在根據hashCode查到具體位置後,將要查詢的物件與對應位置的所有物件做比對,直到查到物件。
這下明白了hashCode的作用,那麼hashCode與equals又有什麼關係呢,先來看看equals方法吧
public boolean equals(Object obj) {
return (this == obj);
}
equals方法採用的是地址比較,至少說是跟hashCode毫無關係。
但是為什麼官方文件會強調重寫了equals,就一定要重寫hashCode,我們先回憶一下,HashMap,HashSet以及HashTable,它們在新增元素的時候,是根據什麼來確定存放位置的。這裡以HashMap為例子,HashMap在確定元素在Node陣列中的位置時,在hash函式中,用hashCode的高16位與低16位做了異或運算。可見,HashMap在確定位置時用到了hashCode。
現在,我們再回到之前的程式碼,hashMap.put(new PhoneNum(1,1,1),“obj1”); 我put進一個key為PhoneNum物件**,Object value=hashMap.get(new PhoneNum(1,1,1));** 現在我又想獲取值。其實在這裡我new了兩個例項,hashCode是不一樣的,HashMap又是根據hashCode定位雜湊桶的位置,這時候,就會出現存的在這個桶,但是在另一個桶找,肯定是找不到的,HashMap就會返回null;那如果存跟找正好在一個桶裡呢,HashMap也會優先比對hashCode,如果hashCode不匹配,也就不會去驗證物件的等同行,直接返回null。
所以,會有重寫equals方法一定要重寫hashCode的規則。