簡體   English   中英

重寫等於非有效散列鍵的強制性

[英]Overridden equals not mandatory for efficient hashing keys

在我看到有關重寫的equals和hashcode方法的所有問題中,人們總是說如果你重寫equals方法,你應該覆蓋hashcode方法,反之亦然,以避免在從Hash集合中獲取對象時出現問題。

我個人並沒有看到反之亦然是為了這個目的,讓我們考慮我們使用一個對象(帶有屬性)作為HashMap中的鍵,我們不需要測試該對象的兩個實例之間的相等性。

如果我們以有效的方式覆蓋hashcode方法(基於屬性和其他規則),那么我們可以擁有唯一鍵,在這種情況下,對於HashMap,我們將在每個桶中具有唯一值,並且HashMap將不使用equals比較存儲桶中的值的方法,因為我們在每個存儲桶中都有一個值。

合同方:

  • 我們的對象的兩個實例是equals(Object.equals one)意味着它們具有相同的引用,並且基於我們的hashcode方法,hashcode將是相同的==> OK
  • 不同的哈希碼會導致不相等的對象==> NOK(在我們的例子中可能會破壞這個規則)但是因為我們有很好的哈希鍵(我們的場景目的),所以合同並不重要

我錯了嗎?

編輯:(搜索后,下面的答案,並查看HashMap的源代碼)

為什么我們也應該重寫equals方法呢?

初始條件,唯一的哈希碼和沒有等於()的重寫

  • 使用get()方法問題檢索值:

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方法使用基於鍵引用的比較,即使哈希碼是相同的。

  • 使用put()方法問題更新值:

如果未覆蓋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。(在本主題之外:))

如果你的類使用Objectequals方法,那么任何冪等的hashCode實現都可以。 如果它使用除Object之外的某個超類的equals方法,那么您需要確保您的hashCode實現與之兼容。

哈希碼的值空間比哈希映射中的桶數大幾個數量級。 因此,除非我們討論像EnumMap這樣的專業Map實現,否則每個桶最終會有多個對象,即使給出了完美的哈希碼。

不覆蓋equals()對象只有當它們是完全相同的對象時才會被視為相等,而不管它們的字段值如何。 因此,僅覆蓋hashCode()是沒有意義的,因為它所能實現的只是輕微的性能損失(至少因為您的自定義哈希代碼需要更長的時間來計算)。 如果自定義哈希碼不完美,它甚至可能是一個很大的性能損失。

每當你覆蓋hashCode()equals() ,你都是這樣做的,因為你希望對象的字段內容很重要。 除非你重寫兩者,否則這種情況不會發生。

在標准Java HashMapequals 始終用於確定密鑰是否在桶中。 如果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覆蓋的細節和你的應用程序使用的實例。 最終結果將是性能的可測量下降。

因此,雖然“反之亦然”並非嚴格要求正確的應用程序行為,但建議仍然有效。 最好是equalhashCode方法具有匹配的語義。

不同的哈希碼導致不相等的對象==>它說“引導”不是說“總是”。 兩個相等的對象總是返回相同的哈希碼,但是如果不覆蓋哈希碼方法,則兩個非相等的對象很難返回相同的哈希碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM