2013-06-20 42 views
6

我讀過這樣一個問題:Changing the elements in a set changes the 'equals' semantics更改值HashSet的

不過,我不知道怎麼解決,我不能在HashSet中改變一個項目,後來刪除的問題。

我有一些示例源代碼:

public static void main(String[] args) { 
    TestClass testElement = new TestClass("1"); 
    Set<TestClass> set = new HashSet<>(); 
    set.add(testElement); 
    printIt(testElement, set, "First Set"); 
    testElement.setS1("asdf"); 
    printIt(testElement, set, "Set after changing value"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set after trying to remove value"); 
    testElement.setS1("1"); 
    printIt(testElement, set, "Set after changing value back"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set removing value"); 
} 

private static void printIt(TestClass hullo, Set<TestClass> set, String message) { 
    System.out.println(message + " (hashCode is " + hullo.hashCode() + "):"); 
    for (TestClass testClass : set) { 
     System.out.println(" " + testClass.toString()); 
     System.out.println("  HashCode: " + testClass.hashCode()); 
     System.out.println("  Element is equal: " + hullo.equals(testClass)); 
    } 
} 

哪裏TestClass的僅僅是持有變量(加上吸氣&二傳手),並具有hashCode()和equals()方法實現的POJO。

有一個請求顯示equals()和hashcode()方法。這些是通過蝕自動生成:

@Override 
public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((s1 == null) ? 0 : s1.hashCode()); 
    return result; 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) 
     return true; 
    if (obj == null) 
     return false; 
    if (getClass() != obj.getClass()) 
     return false; 
    TestClass other = (TestClass) obj; 
    if (s1 == null) { 
     if (other.s1 != null) 
      return false; 
    } else if (!s1.equals(other.s1)) 
     return false; 
    return true; 
} 

結果如下:

First Set (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set after changing value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after trying to remove value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after changing value back (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set removing value (hashCode is 80): 

當哈希碼已經改變,我無法從HashSet的刪除值。正如在linked question,我明白爲什麼它是這樣的,但我不知道如何刪除更改的值。有沒有可能這樣做?

+0

你可以發佈hashcode和equals方法嗎? – mabbas

+0

@mabbas編輯。 – looper

回答

8

您面臨的問題是因爲您的哈希集中的鍵不是不可變的。如果您沒有不可變的密鑰,則一旦修改,您將失去原始密鑰對象的引用。並永遠不能得到處理,這有時被稱爲集合中的內存泄漏。所以如果你使用不可變的密鑰,你不會遇到這種情況。

+0

有趣的是,哈希集合的結果實際上是一個哈希映射的包裝? – robjohncox

+0

@robjohncox這是因爲哈希方式的作品。散列碼用於存儲和檢索對象。假設你創建了一個關鍵對象,並且當你把它放在一個HashMap中時,它的Hashcode方法將被調用來計算Hash並找到要存儲的Hash。當你試圖檢索它時,hashcode被調用來獲得Hash/bucket密鑰存儲在哪裏。如果在存儲在set/map之後更改密鑰對象,則hashcode方法將爲該密鑰對象返回不同的散列值,這與用於存儲密鑰的散列值不同。 –

+1

非常好,但在HashSet(hashmap)中搜索引用不僅哈希碼被使用,而且等於被使用 –

1

testElement添加到HashSet時,它會根據testElement的哈希碼選擇一個存儲桶。當您詢問HashSet是否包含TestElement時,它會計算它正在查找的對象的哈希碼,並僅在該存儲桶中進行搜索。

由於您的hashCode()基於非最終字段,散列碼可以在HashSet的幕後進行更改。因此完全無效HashSet的基本假設。

Testclass的正確實施將使s1字段成爲最終字段。

2

正如您鏈接到細節的問題,以及其他人指出的那樣,您遇到了可變關鍵問題。我會從Javadoc重新報價:

注:大,一定要小心,如果使用可變對象作爲設定 元素。如果以影響等於比較的方式更改對象的值,則不指定對象的行爲,而對象是集合中的元素的對象的組合。

正如您所指出的,您明白了。問題是,在給定情況下,你如何實際移除對象?您不能使用Set.remove(),因爲您的對象在哈希表中丟失。但是,您可以使用Iterator來執行此操作。像下面這樣:

TestClass toRemove = <the same instance, but mutated>; 
for (Iterator<TestClass> iter = set.iterator(); iter.hasNext();) { 
    TestClass item = iter.next(); 
    if (toRemove.equals(item)) { 
    iter.remove(); 
    } 
} 

這種方法依賴於一個事實,即標準equals()方法,如你使用,有一個實例檢查,該檢查將返回true。

請記住,這不是正確的方式來解決這個問題。正確的方法是使用不可變的密鑰或「非常小心」,但它是從HashSet中刪除變異對象的一種方法。