Элемент присутствует, но набор.contains (element` ' возвращает false
как элемент не содержаться в исходном наборе, но в его неизмененном скопировать?
исходный набор не содержит элемент, в то время как его копия. посмотреть изображения.
следующий метод возвращает 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
по какой-то причине. посмотреть изображения.
может кто-нибудь объяснить мне, что происходит?
3 ответов
если вы измените элемент Set
(в вашем случае элементы Set<String>
, поэтому добавление или удаление строки изменит их),Set.contains(element)
может не найти его, поскольку hashCode
элемента будет отличаться от того, что было, когда элемент был сначала добавлен к HashSet
.
при создании нового HashSet
содержащий элементы исходного, элементы добавляются на основе их текущего hashCode
, так что Set.contains(element)
вернет true для нового HashSet
.
вы должны избегать размещения изменяемых экземпляров в 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
наиболее распространенной причиной этого является то, что элемент или ключ был изменен после вставки, что привело к повреждению базовой структуры данных.
Примечание: при добавлении ссылки на Set<String>
в другой Set<Set<String>>
вы добавляете копия!--8-->ссылка базовый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);
если основной 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
ключ для проверки на наличие.
A использует 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}]
так хоть объект is на TreeSet
он не находит его, потому что компаратор не возвращает 0
. Однако, как только он находится в HashSet
все в порядке, потому что HashSet
использует hashCode
чтобы найти его, и они ведут себя правильно.