Jeśli podstawowy Set
był TreeSet
(a może jakaś inna NavigableSet
) to jest możliwe, jeśli obiekty są niedoskonale porównywane, aby tak się stało.
Krytycznym punktem jest to, że HashSet.contains
wygląda następująco:
public boolean contains(Object o) {
return map.containsKey(o);
}
i map
jest HashMap
i HashMap.containsKey
wygląda następująco:
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
więc używa hashCode
klucza do sprawdzenia obecności.
TreeSet
jednak używa TreeMap
wewnętrznie i to containsKey
wygląda następująco:
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
...
Więc używa Comparator
znaleźć klucz.
Tak, w skrócie, jeśli metoda hashCode
nie zgadza się z metodą Comparator.compareTo
(słownie compareTo
powraca 1
podczas gdy hashCode
zwraca różne wartości) wtedy zobaczysz tego rodzaju niejasnych zachowań.
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);
}
drukuje
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}]
więc mimo że obiekt jest w TreeSet
nie jest znalezienie go, ponieważ nigdy nie zwraca 0
komparator. Jednakże, gdy już jest w HashSet
wszystko jest w porządku, ponieważ HashSet
używa hashCode
, aby go znaleźć i zachowują się w prawidłowy sposób.
Nie widzę żadnego dowodu, że 'klastry' to HashSet. Może użyć innej metody "zawiera" – njzk2