【问题标题】:Element is present but `Set.contains(element)` returns false元素存在但 `Set.contains(element)` 返回 false
【发布时间】:2015-08-17 07:59:08
【问题描述】:

一个元素如何包含在原始集合中,但在其未修改副本中?

原始集合不包含该元素,而其副本包含该元素。 See image.

以下方法返回true,尽管它应该总是返回falsecclusters 的实现在这两种情况下都是HashSet

public static boolean confumbled(Set<String> c, Set<Set<String>> clusters) {
    return (!clusters.contains(c) && new HashSet<>(clusters).contains(c));
}

调试显示元素包含在原始文件中,但Set.contains(element)由于某种原因返回falseSee image.

谁能给我解释一下这是怎么回事?

【问题讨论】:

  • 我没有看到任何证据表明 clusters 是一个 HashSet。它可以使用不同的contains 方法

标签: java hash set contains hashset


【解决方案1】:

如果您更改 Set 中的元素(在您的情况下,元素是 Set&lt;String&gt;,因此添加或删除字符串会更改它们),Set.contains(element) 可能无法找到它,因为 hashCode 的该元素将不同于该元素首次添加到HashSet 时的元素。

当您创建一个包含原始元素的新HashSet 时,这些元素会根据它们当前的hashCode 添加,因此Set.contains(element) 将为新的HashSet 返回true。

您应该避免将可变实例放在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

【讨论】:

    【解决方案2】:

    最常见的原因是元素或键在插入后被更改,导致底层数据结构损坏。

    注意:当您将一个Set&lt;String&gt; 的引用添加到另一个Set&lt;Set&lt;String&gt;&gt; 时,您正在添加一个引用 的副本,底层Set&lt;String&gt; 不会被复制,如果您更改它,这些影响您放入的Set&lt;Set&lt;String&gt;&gt; 的更改。

    例如

    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);
    

    【讨论】:

      【解决方案3】:

      如果主要的SetTreeSet(或者可能是其他一些NavigableSet),那么如果您的对象比较不完美,则可能会发生这种情况。

      关键是HashSet.contains看起来像:

      public boolean contains(Object o) {
          return map.containsKey(o);
      }
      

      mapHashMapHashMap.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 返回 1hashCode 返回不同的值),那么您将看到这种晦涩的行为。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-14
        • 2014-02-27
        • 2017-08-18
        • 1970-01-01
        相关资源
        最近更新 更多