代码之家  ›  专栏  ›  技术社区  ›  Gabe Johnson

如何在Java映射中使用SET键

  •  13
  • Gabe Johnson  · 技术社区  · 15 年前

    我有一个映射,它使用一个集合作为密钥类型,如下所示:

    Map<Set<Thing>, Val> map;
    

    当我查询map.containskey(mybunchofthings)时,它返回false,我不明白为什么。我可以遍历keyset中的每个键,并验证是否有一个键(1)具有相同的hashcode,以及(2)等于mybunchofthings。

    System.out.println(map.containsKey(myBunchOfThings)); // false.
    for (Set<Thing> k : map.keySet()) {
      if (k.hashCode() == myBunchOfThings.hashCode() && k.equals(myBunchOfThings) {
         System.out.println("Fail at life."); // it prints this.
      }
    }
    

    我只是从根本上误解了containskey的合同吗?使用集合(或者更普遍地说,集合)作为映射的键有什么秘密吗?

    4 回复  |  直到 7 年前
        1
  •  19
  •   douira    7 年前

    在映射中使用时不应更改键。这个 Map Java文档说明:

    注意:如果 可变对象用作映射键。 未指定映射的行为 如果对象的值被更改 以影响平等的方式 对象是键时的比较 在地图上。这种情况的特例 禁止是指 允许地图包含 作为一把钥匙。虽然它是 允许地图包含 作为一种价值观,极度谨慎是 建议:equals和hashcode 方法不再定义得很好 这样的地图。

    我知道这个问题,但直到现在才做过测试。我再详细解释一下:

       Map<Set<String>, Object> map  = new HashMap<Set<String>, Object>();
    
       Set<String> key1 = new HashSet<String>();
       key1.add( "hello");
    
       Set<String> key2 = new HashSet<String>();
       key2.add( "hello2");
    
       Set<String> key2clone = new HashSet<String>();
       key2clone.add( "hello2");
    
       map.put( key1, new Object() );
       map.put( key2, new Object() );
    
       System.out.println( map.containsKey(key1)); // true
       System.out.println( map.containsKey(key2)); // true
       System.out.println( map.containsKey(key2clone)); // true
    
       key2.add( "mutate" );
    
       System.out.println( map.containsKey(key1)); // true
       System.out.println( map.containsKey(key2)); // false
       System.out.println( map.containsKey(key2clone)); // false (*)
    
       key2.remove( "mutate" );
    
       System.out.println( map.containsKey(key1)); // true
       System.out.println( map.containsKey(key2)); // true
       System.out.println( map.containsKey(key2clone)); // true
    

    key2 是变异的,地图不再包含它。我们可以认为映射在添加数据时会对其进行“索引”,然后我们希望它仍然包含key2克隆(用 * )但有趣的是,事实并非如此。

    因此,正如Java doc所说,密钥不应该被突变,否则行为是 未指定的 . 时期。

    我想你的情况就是这样。

        2
  •  7
  •   reevesy onejigtwojig    10 年前

    您应该努力使用不可变类型作为 Map 集合和集合通常很容易改变,所以用这种方法通常是个坏主意。

    如果要将多个键值用作 地图 密钥应该使用为此目的设计的类实现,如apache commons集合 MultiKey .

    如果确实必须使用集合或集合作为键,请首先使其不可变。( Collections.unmodifiableSet(...) )然后不要保留对可变备份对象的引用。

    使用集合作为键的另一个困难是它们可以按照不同的顺序构造。只有分类的集合才有很可能匹配。例如,如果使用顺序排序的 ArrayList 但是,如果第二次用不同的方法构造列表,它将与密钥不匹配-哈希代码和值的顺序是不同的。

    编辑 :我在下面的声明中得到了更正,从未使用SET作为KET。我刚刚阅读了AbstractHashSet中的一部分hashcode实现。它使用所有值的简单总和,因此不依赖于顺序。equals还检查一个集合是否包含另一个集合中的所有值。然而,Java中其他类型的集合仍然是正确的(ARARYLIST命令确实如此)。

    如果你的收藏实际上是 HashSet ,创建顺序也很重要。事实上,任何类型的散列管理的集合都会有更大的问题,因为任何容量更改都会触发整个集合的重新生成,从而可以对元素重新排序。想想按照冲突发生的顺序存储的哈希的冲突(一个所有元素的简单链接链,其中转换的哈希值相同)。

        3
  •  2
  •   Jorn    15 年前

    插入后是否修改了集合?如果是这样的话,很有可能这一组被分类到一个不同的桶里,而不是它正在寻找的那个。迭代时,它确实会找到您的集合,因为它看起来像整个映射。

    我相信hashmap的合同规定你不能修改用作密钥的对象的hashcode,

        4
  •  0
  •   Leniel Maccaferri    15 年前

    在比较密钥时,是否传递确切的集合(要查找的集合)?