元素存在但 `Set.contains(element)` 返回 false

Posted

技术标签:

【中文标题】元素存在但 `Set.contains(element)` 返回 false【英文标题】:Element is present but `Set.contains(element)` returns false 【发布时间】:2015-08-17 07:59:08 【问题描述】:

一个元素如何包含在原始集合中,但在其未修改副本中?

原始集合不包含该元素,而其副本包含该元素。 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.

谁能给我解释一下这是怎么回事?

【问题讨论】:

我没有看到任何证据表明 clusters 是一个 HashSet。它可以使用不同的contains 方法 【参考方案1】:

如果您更改 Set 中的元素(在您的情况下,元素是 Set&lt;String&gt;,因此添加或删除字符串会更改它们),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

【讨论】:

【参考方案2】:

最常见的原因是元素或键在插入后被更改,导致底层数据结构损坏。

注意:当您将一个Set&lt;String&gt; 的引用添加到另一个Set&lt;Set&lt;String&gt;&gt; 时,您正在添加一个引用 的副本,底层Set&lt;String&gt; 不会被复制,如果您更改它,这些影响您放入的Set&lt;Set&lt;String&gt;&gt; 的更改。

例如

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);

【讨论】:

【参考方案3】:

如果主要的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);

打印

BadThinghash=1 NOT in <TreeSet>[BadThinghash=1, BadThinghash=2]
BadThinghash=2 NOT in <TreeSet>[BadThinghash=1, BadThinghash=2]
BadThinghash=1 is in <HashSet>[BadThinghash=1, BadThinghash=2]
BadThinghash=2 is in <HashSet>[BadThinghash=1, BadThinghash=2]

所以即使对象 TreeSet 中,它也没有找到它,因为比较器永远不会返回 0。但是,一旦它在HashSet 中,一切都很好,因为HashSet 使用hashCode 来找到它,并且它们的行为方式是有效的。

【讨论】:

以上是关于元素存在但 `Set.contains(element)` 返回 false的主要内容,如果未能解决你的问题,请参考以下文章

Swift--Set 详解

如何获取第一个 HTML 元素的文本

List 与 Set 的 contains方法比较

javascript数组去重

java.util.Set.contains(Object o) 的奇怪行为

Java如何循环遍历枚举类元素或者查看某元素是否存在