一、引用相等性與對象相等性 1. 引用相等性 引用到堆上同一個對象的兩個引用是相等的,這就是引用的相等性。 如果想要知道兩個引用是否相等,可以使用==來比較變數上的位元組組合,如果引用到相同的對象,位元組組合也會一樣: 2. 對象相等性 堆上的兩個不同對象在意義上是相同的,這就是對象的相等性。 兩個對象 ...
一、引用相等性與對象相等性
1. 引用相等性
引用到堆上同一個對象的兩個引用是相等的,這就是引用的相等性。
如果想要知道兩個引用是否相等,可以使用==來比較變數上的位元組組合,如果引用到相同的對象,位元組組合也會一樣:
if (foo == bar){ // 兩個引用都指向同一個對象 }
2. 對象相等性
堆上的兩個不同對象在意義上是相同的,這就是對象的相等性。
兩個對象相等的條件是它們的hashCode相等,而且調用以另一個對象為參數的equals時返回true:
if (foo.equals(bar) && foo.hashCode() == bar.hashCode()) { // 兩個引用指向同一個對象或者兩個對象是相等的 }
我們知道所有的類都繼承自Object類,而Object類預設的equals方法是使用==進行比較:
public boolean equals(Object obj) { return (this == obj); }
Object類的hashCode函數:
public native int hashCode();
是一個native函數,而且返回值類型是整形;實際上,該native方法將對象在記憶體中的地址作為哈希碼返回,可以保證不同對象的返回值不同。
因此,要比較兩個對象的相等性,首先需要重寫equals和hashCode方法。
二、equals()與hashCode()的規範與關係
1. equals()方法
JDK中說明瞭實現equals()方法應該遵守的約定:
1)自反性:x.equals(x)必須返回true。
2)對稱性:x.equals(y)與y.equals(x)的返回值必須相等。
3)傳遞性:x.equals(y)為true,y.equals(z)也為true,那麼x.equals(z)必須為true。
4)一致性:如果對象x和y在equals()中使用的信息都沒有改變,那麼x.equals(y)值始終不變。
5)非null:x不是null,y為null,則x.equals(y)必須為false。
2. hashCode()方法
JDK中對hashCode()方法的作用,以及實現時的註意事項做了說明:
1)hashCode()在哈希表中起作用,如java.util.HashMap。
2)如果對象在equals()中使用的信息都沒有改變,那麼hashCode()值始終不變。
3)如果兩個對象使用equals()方法判斷為相等,則hashCode()方法也應該相等。
4)如果兩個對象使用equals()方法判斷為不相等,則不要求hashCode()也必須不相等;但是開發人員應該認識到,不相等的對象產生不相同的hashCode可以提高哈希表的性能。
3. equals()與hashCode()的關係
通過上面兩點我們可以推出結論,如果有兩個對象a,b,如果有:
a.equals(b),則a.hashCode() == b.hashCode()為真,即兩個值相等的對象,他們的hashCode一定相等。
反過來則不一定成立。
實際上在Java中,要證明兩個對象值相等,只要a.equals(b)為true就可以了。
三、hashCode的作用
總的來說,hashCode()在哈希表中起作用,如HashSet、HashMap等。
當我們向哈希表(如HashSet、HashMap等)中添加對象object時,首先調用hashCode()方法計算object的哈希碼,通過哈希碼可以直接定位object在哈希表中的位置(一般是哈希碼對哈希表大小取餘)。如果該位置沒有對象,可以直接將object插入該位置;如果該位置有對象(可能有多個,通過鏈表實現),則調用equals()方法比較這些對象與object是否相等,如果相等,則不需要保存object;如果不相等,則將該對象加入到鏈表中。
這也就解釋了為什麼equals()相等,則hashCode()必須相等。如果兩個對象equals()相等,則它們在哈希表(如HashSet、HashMap等)中只應該出現一次;如果hashCode()不相等,那麼它們會被散列到哈希表的不同位置,哈希表中出現了不止一次。
實際上,在JVM中,載入的對象在記憶體中包括三部分:對象頭、實例數據、填充。其中,對象頭包括指向對象所屬類型的指針和MarkWord,而MarkWord中除了包含對象的GC分代年齡信息、加鎖狀態信息外,還包括了對象的hashcode;對象實例數據是對象真正存儲的有效信息;填充部分僅起到占位符的作用, 原因是HotSpot要求對象起始地址必須是8位元組的整數倍。
四、參考
1. 《Head First Java(第二版)》