元素存在但 `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
,尽管它应该总是返回false
。 c
和clusters
的实现在这两种情况下都是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<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
【讨论】:
【参考方案2】:最常见的原因是元素或键在插入后被更改,导致底层数据结构损坏。
注意:当您将一个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);
【讨论】:
【参考方案3】:如果主要的Set
是TreeSet
(或者可能是其他一些NavigableSet
),那么如果您的对象比较不完美,则可能会发生这种情况。
关键是HashSet.contains
看起来像:
public boolean contains(Object o)
return map.containsKey(o);
而map
是HashMap
和HashMap.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);
打印
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的主要内容,如果未能解决你的问题,请参考以下文章