2015-06-03 66 views
12

如何才能把元素包含在原設定,但其未修改副本`Set.contains(元)`返回false?元素存在,但

原始集合的副本不包含元素。 See image.

以下方法返回true,雖然它應該始終返回falsecclusters的實施在兩種情況下都是HashSet

public static boolean confumbled(Set<String> c, Set<Set<String>> clusters) { 
    return (!clusters.contains(c) && new HashSet<>(clusters).contains(c)); 
} 

調試已表明元件是包含在原始,但Set.contains(element)返回false出於某種原因。 See image.

可能有人請向我解釋這是怎麼回事?

+0

我沒有看到任何證據表明'clusters'是一個HashSet。它可以使用不同的'contains'方法 – njzk2

回答

13

如果更改Set一個元素(在你的案件的元素是Set<String>,因此添加或刪除一個字符串會改變它們),Set.contains(element)可能無法找到它,因爲該元素的hashCode會不同於元素首次添加到HashSet時的情況。

當你創建一個包含原來的元素的新HashSet裏的元素是根據其當前hashCode加入,所以Set.contains(element)將爲新HashSet返回true。

您應該避免將可變實例放入HashSet(或將它們用作HashMap中的鍵),並且如果無法避免,請確保在變更它之前刪除該元素,然後重新添加該元素。否則你的HashSet將被打破。

一個例子:

Set<String> set = new HashSet<String>(); 
set.add("one"); 
set.add("two"); 
Set<Set<String>> setOfSets = new HashSet<Set<String>>(); 
setOfSets.add(set); 
boolean found = setOfSets.contains(set); // returns true 
set.add("three"); 
Set<Set<String>> newSetOfSets = new HashSet<Set<String>>(setOfSets); 
found = setOfSets.contains(set); // returns false 
found = newSetOfSets.contains(set); // returns true 
8

這種情況的最常見的原因是該元件或鍵被插入導致底層數據結構的損壞後改變。

注意:當你添加一個引用到一個Set<String>到另一個Set<Set<String>>要添加的參考,底層Set<String>是不可複製的副本,如果你改變它這些變化影響了Set<Set<String>>你把它變成。

例如

Set<String> s = new HashSet<>(); 
Set<Set<String>> ss = new HashSet<>(); 
ss.add(s); 
assert ss.contains(s); 

// altering the set after adding it corrupts the HashSet 
s.add("Hi"); 
// there is a small chance it may still find it. 
assert !ss.contains(s); 

// build a correct structure by copying it. 
Set<Set<String>> ss2 = new HashSet<>(ss); 
assert ss2.contains(s); 

s.add("There"); 
// not again. 
assert !ss2.contains(s); 
6

如果主SetTreeSet(或者其他一些NavigableSet),那麼它是可能的,如果你的對象是不完全比較,要做到這一點。

關鍵的一點是,HashSet.contains樣子:

public boolean contains(Object o) { 
    return map.containsKey(o); 
} 

mapHashMapHashMap.containsKey樣子:

public boolean containsKey(Object key) { 
    return getNode(hash(key), key) != null; 
} 

所以它使用的關鍵hashCode檢查存在。

一個TreeSet但使用TreeMap內部和它的containsKey樣子:

final Entry<K,V> getEntry(Object key) { 
    // Offload comparator-based version for sake of performance 
    if (comparator != null) 
     return getEntryUsingComparator(key); 
    ... 

所以它採用了Comparator找到鑰匙。

因此,總的來說,如果你hashCode方法不能與你Comparator.compareTo方法達成一致(比如compareTo返回1hashCode返回的值不同),然後你會看到這種模糊的行爲。

class BadThing { 

    final int hash; 

    public BadThing(int hash) { 
     this.hash = hash; 
    } 

    @Override 
    public int hashCode() { 
     return hash; 
    } 

    @Override 
    public String toString() { 
     return "BadThing{" + "hash=" + hash + '}'; 
    } 

} 

public void test() { 
    Set<BadThing> primarySet = new TreeSet<>(new Comparator<BadThing>() { 

     @Override 
     public int compare(BadThing o1, BadThing o2) { 
      return 1; 
     } 
    }); 
    // Make the things. 
    BadThing bt1 = new BadThing(1); 
    primarySet.add(bt1); 
    BadThing bt2 = new BadThing(2); 
    primarySet.add(bt2); 
    // Make the secondary set. 
    Set<BadThing> secondarySet = new HashSet<>(primarySet); 
    // Have a poke around. 
    test(primarySet, bt1); 
    test(primarySet, bt2); 
    test(secondarySet, bt1); 
    test(secondarySet, bt2); 
} 

private void test(Set<BadThing> set, BadThing thing) { 
    System.out.println(thing + " " + (set.contains(thing) ? "is" : "NOT") + " in <" + set.getClass().getSimpleName() + ">" + set); 
} 

打印

BadThing{hash=1} NOT in <TreeSet>[BadThing{hash=1}, BadThing{hash=2}] 
BadThing{hash=2} NOT in <TreeSet>[BadThing{hash=1}, BadThing{hash=2}] 
BadThing{hash=1} is in <HashSet>[BadThing{hash=1}, BadThing{hash=2}] 
BadThing{hash=2} is in <HashSet>[BadThing{hash=1}, BadThing{hash=2}] 

所以即使對象是在它沒有找到它,因爲比較不會返回0TreeSet。但是,一旦它在HashSet中,一切正常,因爲HashSet使用hashCode找到它並且它們的行爲有效。