equals方法
Object類中默認的實現方式是 : return this == obj 。那就是說,只有this 和 obj引用同一個對象,纔會返回true。
而我們往往需要用equals來判斷 2個對象是否等價,而非驗證他們的唯一性。這樣我們在實現自己的類時,就要重寫equals.
按照約定,equals要滿足以下規則。
自反性: x.equals(x) 一定是true
對null: x.equals(null) 一定是false
對稱性: x.equals(y) 和 y.equals(x)結果一致
傳遞性: a 和 b equals , b 和 c equals,那麼 a 和 c也一定equals。
一致性: 在某個運行時期間,2個對象的狀態的改變不會影響equals的決策結果,那麼,在這個運行時期間,無論調用多少次equals,都返回相同的結果。
hashCode方法
hashCode()方法是從Object類中繼承過來的,它也用來鑑定兩個對象是否相等。Object類中的hashCode()方法返回對象在內存中地址轉換成的一個int值,所以如果沒有重寫hashCode()方法,任何對象的hashCode()方法都是不相等的。
對象的散列碼是爲了更好的支持基於哈希機制的Java集合類,例如 Hashtable, HashMap, HashSet 等。
關於hashCode方法,一致的約定是:
在某個運行時期間,只要對象的(字段的)變化不會影響equals方法的決策結果,那麼,在這個期間,無論調用多少次hashCode,都必須返回同一個散列碼。
如果2個對象通過equals調用後返回是true,那麼這個2個對象的hashCode方法也必須返回同樣的int型散列碼如果2個對象通過equals返回false,他們的hashCode返回的值允許相同。(然而,程序員必須意識到,hashCode返回獨一無二的散列碼,會讓存儲這個對象的hashtables更好地工作。)
equals和hashcode區別
一般來講,equals()方法是給用戶調用的,如果需要判斷兩個對象是否相等的,可以重寫equals()方法,然後在代碼中調用,這樣就可以判斷它們是否相等了。
對於hashCode()方法,用戶一般不會去調用它,例如在hashmap中,由於key是不可以重複的,它在判斷key是否重複時就判斷了hashCode()方法,而且也用到了equals()方法。此處“不可以重複”指的是equals()和hashCode()只要有一個不等就可以了。所以,hashCode()方法相當於是一個對象的編碼,就好像文件中的md5,它與equals()方法的不同之處就在於它返回的是int型,比較起來不直觀。
hashCode()方法和equals()方法的關係如下
1.若兩個對象equals返回true,則hashCode有必要也返回相同的int數。
2.若兩個對象equals返回false,則hashCode不一定返回不同的int數,但爲不相等的對象生成不同hashCode值可以提高哈希表的性能。
3..若兩個對象hashCode返回相同int數,則equals不一定返回true。
4.若兩個對象hashCode返回不同int數,則equals一定返回false。
5.一般在覆蓋equals()方法的同時也要覆蓋hashCode()方法,否則,就會違反Object.hashCode的通用約定,從而導致該類無法與所有基於散列值(hash)集合類(HashMap、HashSet和Hashtable)結合在一起正常運行。
爲什麼覆蓋equals()方法的同時要覆蓋hashCode()方法?
Object.hashCode的通用約定
1.在一個應用程序執行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,那麼,對該對象調用hashCode方法多次,它必須始終如一地返回 同一個整數。在同一個應用程序的多次執行過程中,這個整數可以不同,即這個應用程序這次執行返回的整數與下一次執行返回的整數可以不一致。
2.如果兩個對象根據equals(Object)方法是相等的,那麼調用這兩個對象中任一個對象的hashCode方法必須產生同樣的整數結果。
3.如果兩個對象根據equals(Object)方法是不相等的,那麼調用這兩個對象中任一個對象的hashCode方法,不要求必須產生不同的整數結果。然而,程序員應該意識到這樣的事實,對於不相等的對象產生截然不同的整數結果,有可能提高散列表(hash table)的性能。以Java.lang.Object來理解, JVM每次new一個Object, 都會將Object丟到一個哈希表中去, 這樣的話,下次做Object的比較或者取這個對象的時候, 它會根據對象的hashcode再從Hash表中取這個對象。這樣做的目的是提高取對象的效率。
如果只重寫了equals方法而沒有重寫hashCode方法的話,則會違反約定的第二條:相等的對象必須具有相等的散列碼(hashCode)。
同時對於HashSet和HashMap這些基於散列值(hash)實現的類。HashMap的底層處理機制是以數組的方法保存放入的數據的(Node<K,V>[] table),其中的關鍵是數組下標的處理。數組的下標是根據傳入的元素hashCode方法的返回值再和特定的值異或決定的。如果該數組位置上已經有放入的值了,且傳入的鍵值相等則不處理,若不相等則覆蓋原來的值,如果數組位置沒有條目,則插入,並加入到相應的鏈表中。檢查鍵是否存在也是根據hashCode值來確定的。所以如果不重寫hashCode的話,可能導致HashSet、HashMap不能正常的運作、
如果我們將某個自定義對象存到HashMap或者HashSet及其類似實現類中的時候,如果該對象的屬性參與了hashCode的計算,那麼就不能修改該對象參數hashCode計算的屬性了。有可能會移除不了元素,導致內存泄漏。
看一個代碼片段:
運行這段代碼發現結果返回的是null。
原因:未重寫hashcode,所以兩個new Apple("green")的hashcode值不同,無法得到對應的對象。
我們來看下HashMap中的get源碼進行理解:
1.new Object(),JVM根據這個對象的Hashcode值,放入到對應的Hash表對應的Key上,如果不同的對象確產生了相同的hash值,也就是發生了Hash key相同導致衝突的情況,那麼就在這個Hash key的地方產生一個鏈表,將所有產生相同hashcode的對象放到這個單鏈表上去,串在一起。
2.比較兩個對象的時候,首先根據他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,那麼就是說他們這兩個對象放在Hash表中的同一個key上,那麼他們一定在這個key上的鏈表上。那麼此時就只能根據Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不同的話,肯定他們不能equals.
重寫hashCode時注意事項
重寫hashCode方法時除了上述一致性約定,還有以下幾點需要注意:
(1)返回的hash值是int型的,防止溢出。
(2)不同的對象返回的hash值應該儘量不同。(爲了hashMap等集合的效率問題)
(3)《Java編程思想》中提到一種情況
“設計hashCode()時最重要的因素就是:無論何時,對同一個對象調用hashCode()都應該產生同樣的值。如果在講一個對象用put()添加進HashMap時產生一個hashCdoe值,而用get()取出時卻產生了另一個hashCode值,那麼就無法獲取該對象了。所以如果你的hashCode方法依賴於對象中易變的數據,用戶就要當心了,因爲此數據發生變化時,hashCode()方法就會生成一個不同的散列碼”。