簡體   English   中英

我應該覆蓋集合的hashCode()嗎?

[英]Should I override hashCode() of Collections?

鑒於我有一些課程,其中包含各種領域:

class MyClass {
    private String s;
    private MySecondClass c;
    private Collection<someInterface> coll;
    // ...

    @Override public int hashCode() {
        // ????
    }
}

而且,我確實有各種各樣的對象,我想將它們存儲在HashMap 為此,我需要擁有MyClasshashCode()

  1. 我將不得不遞歸地進入所有字段和相應的父類,以確保它們都正確實現hashCode() ,因為否則MyClass hashCode()可能不會考慮某些值。 這是正確的嗎?

  2. 我該怎么處理這個Collection 我可以一直依賴hashCode()方法嗎? 它會考慮我的someInterface對象中可能存在的所有子值嗎?


我在這里打開了關於唯一ID對象的實際問題的第二個問題: 如何為對象生成(幾乎)唯一的哈希ID?


澄清:

你班上有什么或多或少不合理的東西? 字符串s? 然后只將其用作哈希碼。

如果其中一個對象的coll的任何值發生更改,則兩個對象的MyClass hashCode()肯定會有所不同。 如果兩個對象的所有字段都存儲相同的值,HashCode應該只返回相同的值。 基本上,在MyClass對象上進行一些耗時的計算。 如果計算已經使用完全相同的值在前一段時間完成,我想多余時間。 為此,如果結果已經可用,我想查看HashMap。

你會在HashMap中使用MyClass作為鍵還是值? 如果是鍵,則必須覆蓋equals()和hashCode()

因此,我使用hashCode OF MyClass作為HashMap中的 值(計算結果)將是不同的,如整數(簡化)。

您認為平等對多個館藏意味着什么? 它應該依賴於元素排序嗎? 它應該只取決於存在的絕對元素嗎?

這不會取決於存儲在coll的Collection類型嗎? 雖然我覺得訂購並不重要,不

你從這個網站得到的回應是華麗的。 謝謝你們

@AlexWien取決於該集合的項是否是該類的等價定義的一部分。

是的,是的,他們是。

  1. 我將不得不遞歸地進入所有字段和相應的父類,以確保它們都正確實現hashCode() ,因為否則MyClass hashCode()可能不會考慮某些值。 這是正確的嗎?

那是對的。 它並不像它聽起來那么繁重,因為經驗法則是你只需覆蓋hashCode()如果你重寫equals() 您不必擔心使用默認equals() ; 默認的hashCode()就足夠了。

此外,對於您的類,您只需要在equals()方法中散列您比較的字段。 例如,如果其中一個字段是唯一標識符,則只需在equals()檢查該字段並在hashCode()對其進行散列即可。

所有這一切都取決於你是否也覆蓋了equals() 如果你沒有覆蓋它,也不要打擾hashCode()

  1. 我該怎么處理這個Collection 我可以一直依賴hashCode()方法嗎? 它會考慮我的someInterface對象中可能存在的所有子值嗎?

是的,您可以依賴Java標准庫中的任何集合類型來正確實現hashCode() 是的,任何ListSet都會考慮其內容(它會將項目的哈希碼混合在一起)。

所以,你想要做你的對象的內容,會給你一個獨特的鍵,你就可以在檢查一個計算HashMap是否是你不想做兩次已經完成對“重”計算給定的深層組合字段。

單獨使用hashCode

我相信hashCode不適合在你描述的場景中使用。

hashCode始終equals()關聯使用。 它是它的契約的一部分,它是一個重要的部分,因為hashCode()返回一個整數,雖然可能會嘗試使hashCode()盡可能地分布,但它並不是唯一的每個可能的對象相同的類,除了非常具體的情況(例如, IntegerByteCharacter很容易)。

如果您想親眼看看,請嘗試生成最多4個字母(大寫和小寫)的字符串,並查看其中有多少個具有相同的哈希碼。

因此, HashMap在哈希表中查找內容時會同時使用hashCode()equals()方法。 將有一些元素具有相同的hashCode() ,你只能通過使用equals()對你的類測試所有元素來判斷它是否是相同的元素。

使用hashCodeequals一起使用

在此方法中,您將對象本身用作哈希映射中的鍵,並為其提供適當的equals方法。

要實現equals方法,您需要深入了解所有字段。 它們的所有類必須具有equals() ,它們與您認為相同的東西相匹配,以便進行大計算。 當對象實現接口時,需要特別小心。 如果計算基於對該接口的調用,並且實現該接口的不同對象在這些調用中返回相同的值,那么它們應該以反映該值的方式實現equals

並且他們的hashCode應該匹配equals - 當值相等時, hashCode必須相等。

然后基於所有這些項構建equalshashCode 您可以使用Objects.equals(Object, Object)Objects.hashCode( Object...)來節省很多樣板代碼。

但這是一個好方法嗎?

雖然你可以在對象中緩存hashCode()的結果並重新使用它而不進行計算,只要你不改變它,你就不能為equals做到這一點。 這意味着equals計算將是漫長的。

因此,根據為每個對象調用equals()方法的次數,這將會加劇。

例如,如果你將在hashMap擁有30個對象,但是300,000個對象將會出現並與它們進行比較只是為了意識到它們與它們相等,那么你將進行300,000次重比較。

如果你只有很少的實例,其中一個對象將具有相同的hashCode或落入HashMap中的同一個桶中,需要進行比較,那么采用equals()方式可能效果很好。

如果你決定這樣做,你需要記住:

如果對象是HashMap的鍵,則只要它在那里就不應該變異 如果您需要改變它,您可能需要對其進行深層復制並將副本保留在哈希映射中。 深度復制需要考慮內部的所有對象和接口,以查看它們是否可以復制。

為每個對象創建唯一鍵

回到最初的想法,我們已經確定hashCode不是哈希映射中鍵的良好候選者。 一個更好的候選者是散列函數,如md5sha1 (或更高級的散列,如sha256,但你不需要加密強度),其中碰撞比僅僅是int更少見。 您可以獲取類中的所有值,將它們轉換為字節數組,使用此類哈希函數對其進行哈希處理,並將其十六進制字符串值作為映射鍵。

當然,這不是一個微不足道的計算。 因此,您需要考慮它是否真的為您節省了大量時間而無法避免。 它可能比重復調用equals()來比較對象更快,因為每個實例只執行一次,使用“大計算”時的值。

對於給定的實例,您可以緩存結果,而不是再次計算它,除非您改變對象。 或者你可以在進行“大計算”之前再次計算它。

但是,您需要在課堂上擁有所有對象的“合作”。 也就是說,它們都需要合理地轉換為字節數組,使得兩個等效對象產生相同的字節(包括與上面提到的接口對象相同的問題)。

您還應該注意你擁有的情況,例如,兩個字符串“AB”和“CD”,它們會給你與“A”和“BCD”相同的結果,然后你最終會得到相同的哈希兩個不同的對象。

對於未來的讀者。

是的,equals和hashCode齊頭並進。

下面顯示了使用幫助程序庫的典型實現,但它確實顯示了“手拉手”的性質。 來自apache的幫助程序庫使事情更簡單恕我直言:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    MyCustomObject castInput = (MyCustomObject) o;

    boolean returnValue = new org.apache.commons.lang3.builder.EqualsBuilder()
            .append(this.getPropertyOne(), castInput.getPropertyOne())
            .append(this.getPropertyTwo(), castInput.getPropertyTwo())
            .append(this.getPropertyThree(), castInput.getPropertyThree())
            .append(this.getPropertyN(), castInput.getPropertyN())
            .isEquals();

    return returnValue;
}

@Override
public int hashCode() {
    return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
            .append(this.getPropertyOne())
            .append(this.getPropertyTwo())
            .append(this.getPropertyThree())
            .append(this.getPropertyN())
            .toHashCode();
}    

17,37 ..那些你可以選擇自己的價值觀。

從您的澄清:

您希望將MyClass存儲在HashMap作為鍵。 這意味着在添加對象后不允許更改hashCode ()。 因此,如果您的集合可能在對象實例化后發生更改,則它們不應該是hashcode()的一部分。

來自http://docs.oracle.com/javase/8/docs/api/java/util/Map.html

注意:如果將可變對象用作映射鍵,則必須非常小心。 如果在對象是地圖中的鍵的情況下以影響等於比較的方式更改對象的值,則不指定映射的行為。

對於20-100個對象,不值得您輸入不一致的hash()或equals()實現的風險。

在您的情況下,不需要覆蓋hahsCode()和equals()。 如果你沒有覆蓋它,java會獲取equals和hashcode()的唯一對象標識(這是有效的,因為你聲明你不需要考慮對象字段值的equals())。

使用默認實現時,您可以放心使用。

在插入后哈希碼更改時,使用自定義哈希碼()作為HashMap中的鍵時發生錯誤,因為您使用集合的哈希碼()作為對象哈希碼的一部分可能會導致極難找到錯誤。

如果你需要弄清楚重計算是否已經完成,我就不會使等於() 只需編寫一個自己的方法objectStateValue()並在集合上調用hashcode()。 這樣就不會干擾對象hashcode和equals()。

public int objectStateValue() {
    // TODO make sure the fields are not null;
 return 31 * s.hashCode() + coll.hashCode();
}

另一個更簡單的可能性:執行耗時計算的代碼可以在計算准備就緒后立即將計算器計數器提高一個。 然后,您只需檢查計數器是否已更改。 這更便宜,更簡單。

暫無
暫無

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

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