如果主要的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 来找到它,并且它们的行为方式是有效的。