Элемент присутствует, но набор.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 чтобы найти его, и они ведут себя правильно.