如何将元素不包含在原始集中,而是包含在未修改的副本中?
原始集合在其副本的同时不包含该元素。 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
由于某种原因返回<property name="s" value="true
。 See image.
有人可以向我解释发生了什么吗?
答案 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)
如果主要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);
}
打印
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
来查找它并且它们的行为方式有效。