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

时间:2015-06-03 08:17:43

标签: java hash set contains hashset

如何将元素包含在原始集中,而是包含在未修改的副本中?

原始集合在其副本的同时不包含该元素。 See image.

以下方法返回true,但它应始终返回false。在c两种情况下clustersHashSet的实施都是public static boolean confumbled(Set<String> c, Set<Set<String>> clusters) { return (!clusters.contains(c) && new HashSet<>(clusters).contains(c)); }

Set.contains(element)

调试显示原始中包含 元素,但false由于某种原因返回<property name="s" value="trueSee image.

有人可以向我解释发生了什么吗?

3 个答案:

答案 0 :(得分:15)

如果您更改Set中的元素(在您的情况下元素为Set<String>,那么添加或删除字符串会更改它们),Set.contains(element)可能无法找到它,因为元素的hashCode将不同于首次将元素添加到HashSet时的元素。

当您创建包含原始元素的新HashSet时,会根据当前hashCode添加元素,因此Set.contains(element)将为新{{1}返回true }}

您应该避免将可变实例放在HashSet中(或将它们用作HashSet中的键),如果您无法避免,请确保在变异之前删除该元素然后重新添加它。否则,您的HashMap将被破坏。

一个例子:

HashSet

答案 1 :(得分:8)

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

注意:当您将Set<String>的引用添加到另一个Set<Set<String>>时,您要添加引用的副本,则不会复制基础Set<String>如果你改变了这些影响你所投入的Set<Set<String>>的变化。

e.g。

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

答案 2 :(得分:7)

如果主要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返回1,而hashCode返回不同的值)然后你会看到这种模糊的行为。

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}]

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