[英]Overridden equals not mandatory for efficient hashing keys
在我看到有關重寫的equals和hashcode方法的所有問題中,人們總是說如果你重寫equals方法,你應該覆蓋hashcode方法,反之亦然,以避免在從Hash集合中獲取對象時出現問題。
我個人並沒有看到反之亦然是為了這個目的,讓我們考慮我們使用一個對象(帶有屬性)作為HashMap中的鍵,我們不需要測試該對象的兩個實例之間的相等性。
如果我們以有效的方式覆蓋hashcode方法(基於屬性和其他規則),那么我們可以擁有唯一鍵,在這種情況下,對於HashMap,我們將在每個桶中具有唯一值,並且HashMap將不使用equals比較存儲桶中的值的方法,因為我們在每個存儲桶中都有一個值。
合同方:
我錯了嗎?
編輯:(搜索后,下面的答案,並查看HashMap的源代碼)
為什么我們也應該重寫equals方法呢?
初始條件,唯一的哈希碼和沒有等於()的重寫
Object.equals()執行引用比較,並且此方法用於put和get方法
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
如果我們不重寫equals,如果我們丟失了鍵的引用,例如對於另一個代碼不可見的鍵引用,我們將無法從HashMap中檢索值。 讀取HashMap的唯一方法是使用Iterator,並比較值以檢索確切的對:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Steven”)]
Second put: [key:Instance2Obj(17488696), value:Person2Obj(“Hewlett”, “Emily”)]
如果我們想根據他的帳號檢索Baron Steven的帳戶,我們不僅可以創建一個具有相同帳號的新對象,例如Instance88Obj(144756696),並調用get方法來檢索該值。 在這種情況下,我們將得到一個null,因為get方法使用基於鍵引用的比較,即使哈希碼是相同的。
如果未覆蓋equals,則通過新的實例鍵(但具有相同的哈希碼)為了更新它而放置一個新值將不起作用,只會添加另一對鍵/值:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Seven”)]
Second put: [key:Instance2Obj(144756696), value:Person2Obj(“Baron”, “Steven”)]
通常我們期望用“史蒂文”替換“七”值,但替換不會發生
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
結論:
==>如果覆蓋哈希碼,則覆蓋等於。
==>如果你重寫equals,也可以覆蓋hashcode。(在本主題之外:))
如果你的類使用Object
的equals
方法,那么任何冪等的hashCode
實現都可以。 如果它使用除Object
之外的某個超類的equals
方法,那么您需要確保您的hashCode
實現與之兼容。
哈希碼的值空間比哈希映射中的桶數大幾個數量級。 因此,除非我們討論像EnumMap
這樣的專業Map
實現,否則每個桶最終會有多個對象,即使給出了完美的哈希碼。
不覆蓋equals()
對象只有當它們是完全相同的對象時才會被視為相等,而不管它們的字段值如何。 因此,僅覆蓋hashCode()
是沒有意義的,因為它所能實現的只是輕微的性能損失(至少因為您的自定義哈希代碼需要更長的時間來計算)。 如果自定義哈希碼不完美,它甚至可能是一個很大的性能損失。
每當你覆蓋hashCode()
和equals()
,你都是這樣做的,因為你希望對象的字段內容很重要。 除非你重寫兩者,否則這種情況不會發生。
在標准Java HashMap
, equals
始終用於確定密鑰是否在桶中。 如果hashCode
只為一個項提供右桶,但equals
對該一個鍵返回false,則表示該鍵不在那里。
如果不重寫equals
,則使用對象引用。 如果要將對象的值用作鍵(即,可以使用具有相同值的不同對象實例),而不是要求完全相同的對象實例(相同的實例不相等),則需要編寫實現比較值的equals
。
默認的hashCode
與默認的equals
一樣高效。 覆蓋hashCode
可能會降低性能,而不會改變行為( equals
仍會確定兩個關鍵對象是否相等)。 這樣做毫無意義。
你沒有問這個,但另外,如果你覆蓋equals
而不重寫hashCode
,那么你就破壞了 HashMap
密鑰,因為hashCode
可能會把你帶到錯誤的桶,而equals
將找不到密鑰,因為它只會檢查一個桶。
您可以做什么:如果您可以為每個項目保證不同的hashCode
,您可以編寫僅比較哈希碼的equals
。 如果將哈希代碼緩存在對象中,那么這可以提高性能,使得equals
僅比較兩個整數。 當然,還有內存開銷。
可以避免實現equals,或者避免實現hashcode。 在這兩種情況下,你都可以打開一個充滿痛苦的世界。 畢竟記住Hash用於將對象分成桶,然后桶中的對象仍然需要檢查是否相等,您依賴於您正在使用的集合對象以您需要的方式運行。
這些指導原則可以防止出現很多問題,沒有充分的理由可以避免使用它們,除了嘗試並且聰明一點,這樣做可以在未來為自己打開一大堆問題。
合同說如果兩個對象相等,它們應該具有相同的哈希碼。 但另外,如果不相等的對象應該具有不同的哈希碼,則哈希表最有效。
如果覆蓋默認的hashCode
方法而不覆蓋默認的equals
方法,則可能會使hashcode
的行為不理想; 即給出更多情況,其中hashCode為不相等的對象返回相同的值。 這可能會在哈希表中給你更多的沖突,具體取決於你的hashCode
覆蓋的細節和你的應用程序使用的實例。 最終結果將是性能的可測量下降。
因此,雖然“反之亦然”並非嚴格要求正確的應用程序行為,但建議仍然有效。 最好是equal
和hashCode
方法具有匹配的語義。
不同的哈希碼導致不相等的對象==>它說“引導”不是說“總是”。 兩個相等的對象總是返回相同的哈希碼,但是如果不覆蓋哈希碼方法,則兩個非相等的對象很難返回相同的哈希碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.