簡體   English   中英

hashCode()只應使用equals()中使用的不可變字段的子集嗎?

[英]Should hashCode() only use the subset of immutable fields of those used in equals()?

情況

我需要覆蓋equals() ,因為我建議我也使用相同的字段覆蓋hashCode()方法。 然后,當我看着一個只包含一個對象的集合時,我得到了令人沮喪的結果

set.contains(object)
=> false

set.stream().findFirst().get().equals(object)
=> true

我現在明白了,這是因為在將object添加到set之后對object所做的更改再次更改了其hashCode。 然后contains查找錯誤的鍵並找不到該object

我對實施的要求是

  • 需要可變字段才能正確實現equals()
  • 即使它們容易發生變化,也可以在基於散列的CollectionsMaps安全地使用這些對象,例如灰燼HashSet

這違反了慣例

僅使用equals()中使用的字段子集計算hashCode()而不是全部使用是否存在任何危險?

更具體而言,這將意味着: equals()使用了編號的對象,而字段的hashCode()只使用了在用於這些領域equals()和是不可變的

我認為這應該沒問題,因為

  • 合同是滿的:相等的對象將產生相同的hashCode,而相同的hashCode並不意味着對象是相同的。
  • 即使對象暴露於更改,對象的hashCode也保持不變,因此可以在這些更改之前和之后的HashSet找到。

相關帖子幫助我理解了我的問題,但沒有解決方法: 在Java中覆蓋equals和hashCode時應該考慮哪些問題? equals和hashcode的不同字段

合同確實會實現。 合同強制.equal()對象始終具有相同的.hashCode() 相反的情況不一定是真的,我想知道一些人和IDE的痴迷恰好適用於這種做法。 如果所有可能的組合都可以,那么你會發現完美的哈希函數。

順便說一句,IntelliJ在生成hashCode時提供了一個很好的向導,並且通過分別處理這兩個方法並允許區分您的選擇來提供相同的向導。 顯然,相反,又名提供在更多領域hashCode()和更少的領域中equals()違反約定。

對於HashSet和類似的集合/映射,使用hashCode()只使用equals()方法中的字段子集是一種有效的解決方案。 當然,您必須考慮哈希碼有效減少地圖中的沖突。

但請注意,如果要使用TreeSet等有序集合,問題就會出現。 然后你需要一個永遠不會給“不同”對象發生沖突(返回零)的比較器,這意味着該集合只能包含一個碰撞元素。 你的equals()描述意味着存在多個只在可變字段中不同的對象,然后你輸了:

  • 在compareTo()方法中包含可變字段可以更改比較符號,以便對象需要移動到樹中的不同分支。
  • 排除compareTo()方法中的可變字段會限制您在TreeSet中擁有最多一個碰撞元素。

所以我強烈建議再次考慮你的對象類的平等和可變性的概念。

這對我來說完全有效。 假設你有一個Person

 final int name; // used in hashcode
 int income; // name + income used in equals

name決定條目的位置(想想HashMap )或選擇哪個桶。

你把一個Person作為Key放在HashMap :根據hashcode它會進入某個桶,例如第二個。 您升級income並在地圖中搜索該Person 根據hashcode它必須在第二個桶中,但根據equals它不存在:

 static class Person {
    private final String name;

    private int income;

    public Person(String name) {
        super();
        this.name = name;
    }

    public int getIncome() {
        return income;
    }

    public void setIncome(int income) {
        this.income = income;
    }

    public String getName() {
        return name;
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        Person right = (Person) other;

        return getIncome() == right.getIncome() && getName().equals(right.getName());
    }

}

並測試:

    HashSet<Person> set = new HashSet<>();
    Person bob = new Person("bob");
    bob.setIncome(100);
    set.add(bob);

    Person sameBob = new Person("bob");
    sameBob.setIncome(200);

    System.out.println(set.contains(sameBob)); // false

您認為缺少的是hashcode決定一個條目所在的桶(該桶中可能有許多條目),這是第一步,但是equals決定它是否合適,是一個相等的條目。

您提供的示例完全合法; 但你鏈接的那個是另一種方式 - 它在hashcode使用更多的字段,因此它是錯誤的。

如果您了解這些細節,那么第一個哈希碼用於了解Where和Entry 可能駐留的位置,並且只有稍后所有這些(來自子集或存儲桶)都試圖通過equal方式找到 - 您的示例才有意義。

hashCode()可以使用equals()使用的字段的子集,盡管它可能會給你一點性能下降。

您的問題似乎是由於修改了對象,同時仍然在集合內部,以改變hashCode()和/或equals() 無論何時將對象添加到HashSet(或作為HashMap中的鍵),都不得隨后修改equals()和/或hashCode()使用的該對象的任何字段。 理想情況下, equals()使用的所有字段都應該是final 如果它們不可能,則必須將它們視為最終,同時對象在集合中。

TreeSet / TreeMap也是如此,但適用於compareTo()使用的字段。

如果您確實需要修改equals()使用的字段(或者在TreeSet / TreeMap的情況下通過compareTo() ),您必須:

  1. 首先,從集合中刪除該對象;
  2. 然后修改對象;
  3. 最后將它添加回集合中。

暫無
暫無

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

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