簡體   English   中英

HashSet.contains 不應該返回 false

[英]HashSet.contains returns false when it shouldn't

我有這個代碼:

public class Tray {

private Set<Block> blocks;

private int numColumns;
private int numRows;

//constructor
public Tray (int numRows, int numColumns){
    this.numColumns = numColumns;
    this.numRows = numRows;
    blocks = new HashSet<>();
}

public boolean satisfiesGoal(Tray other){

    Block thisBlock = this.blocks.iterator().next();
    Block otherBlock = other.blocks.iterator().next();

    boolean hashesEqual = thisBlock.hashCode() == otherBlock.hashCode(); // this is true

    boolean areEqual = thisBlock.equals(otherBlock) && otherBlock.equals(thisBlock); // this is true

    boolean contains = this.blocks.contains(otherBlock); // this is false, why?
    return contains;
}

在主要方法中,我已將 2 個塊添加到各自的托盤中。 根據調試器,變量“hashesEqual”和“areEqual”為真,但布爾值“contains”為假。 關於為什么根據“equals”方法,2 個對象的哈希值會相等,但不會在 HashSet 中包含相等對象的任何想法?

如果在將對象添加到 HashSet以影響它們的相等性和哈希碼的方式修改對象,則會發生此問題。 由於未在哈希表的正確槽中找到與其新值相對應的對象,該集合將出現故障。

同樣,如果您修改用作鍵的對象,HashMap 也會發生故障。 與 TreeSet 和 TreeMap 類似。 所有這些數據結構都可以快速定位對象,因為每個對象的值決定了它的存儲位置。 如果這些對象隨后被修改,結構就會出錯。

不可變對象作為集合元素和映射鍵更好,因為它們避免了這種復雜性。

如果您必須修改屬於對象相等性一部分的字段,則需要先將其暫時從集合中刪除,然后再添加。 或者,使用列表作為對象的主要容器,並僅在需要時構造一個臨時集。

Block 對象是可變的嗎?

HashSet 將其內容作為鍵存儲在 HashMap 中,並以任意對象作為值。

根據這篇文章,

如果一個對象的 hashCode() 值可以根據它的狀態改變,那么在基於散列的集合中使用這樣的對象作為鍵時我們必須小心[強調我的] 以確保我們不允許它們的狀態在它們被改變時改變。用作散列鍵。 所有基於散列的集合都假定對象的散列值在用作集合中的鍵時不會更改。 如果鍵的哈希碼在集合中發生更改,則可能會出現一些不可預測且令人困惑的后果。 這在實踐中通常不是問題——使用像 List 這樣的可變對象作為 HashMap 中的鍵不是常見的做法。

openjdk 中contains的代碼非常簡單——它最終只調用HashMap.getEntry ,它使用哈希碼並等於檢查鍵是否存在。 我的猜測是您的錯誤在於認為該項目已經在集合中。 但是您可以通過直接在您發布的代碼中聲明一個Set並將項目添加到該集合中來輕松確認這是錯誤的。

嘗試添加以下單元測試:

Set<Block> blocks = new HashSet<>();
blocks.add(thisBlock);
assertTrue(thisBlock.hashCode() == otherBlock.hashCode() && thisBlock.equals(otherBlock));
assertTrue(blocks.contains(otherBlock));

如果第一個斷言通過而第二個斷言失敗,那么您就發現了 Java 中的一個錯誤。 我覺得這不太可能。

還要確保您有可用的 openjdk 源代碼,以便您可以在調試時進入 Java 方法。 這樣你就可以進入contains並准確檢查它失敗的地方。

另請注意,您的代碼this.blocks.iterator().next()每次調用該函數時都會創建一個新的迭代器,然后返回迭代中的第一項。 換句話說,它選擇集合中的第一項(請注意,這不是最不自然的順序)。 如果您嘗試按順序遍歷兩個集合並比較值,那么這不是您的代碼目前所做的。

暫無
暫無

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

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