如何使用在Java Maps中设置为键

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用在Java Maps中设置为键相关的知识,希望对你有一定的参考价值。

我有一个使用Set作为键类型的Map,如下所示:

Map<Set<Thing>, Val> map;

当我查询map.containsKey(myBunchOfThings)时,它返回false,我不明白为什么。我可以遍历键集中的每个键,并验证是否有一个键(1)具有相同的hashCode,(2)对于myBunchOfThings是equals()。

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的合约?使用集合(或更一般地说,集合)作为地图的关键是否有秘密?

答案

在地图中使用时,不应突变键。 Map java doc说:

注意:如果将可变对象用作映射键,则必须非常小心。如果在对象是地图中的键的情况下以影响等于比较的方式更改对象的值,则不指定映射的行为。这种禁令的一个特例是地图不允许将自己作为关键词包含在内。虽然允许映射将自身包含为值,但建议极其谨慎: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所说,密钥不应该被改变,否则行为是未指定的。期。

我想这就是你的情况。

另一答案

您应该努力使用不可变类型作为Maps的键。集合和集合通常很容易变化,因此使用这种方式通常是个坏主意。

如果你想使用许多键值作为Map键,你应该使用为此目的设计的类实现,如Apache Commons Collections MultiKey

如果你真的必须使用Set或Collection作为键,首先使它成为不可变的(Collections.unmodifiableSet(...)),然后不要保留对可变后备对象的引用。

使用Collections作为键的另一个困难是它们可以以不同的顺序构造。只有排序的集合才会有很高的匹配率。例如,如果您使用顺序排序的ArrayList但是以不同的方式构造列表,则第二次它将与键不匹配 - 哈希码和值的顺序是不同的。

编辑:我在下面的声明中得到纠正,从来没有必要使用Set for a ket。我刚刚在AbstractHashSet中读取了一部分hashCode实现。这使用了所有值的简单总和,因此不依赖于顺序。 Equals还会检查一组是否包含另一组中的所有值。但是,对于Java中的其他类型的集合,这仍然是正确的(ArrayList顺序确实很重要)。

如果您的集合实际上是HashSet,创建顺序也很重要。实际上,任何类型的哈希管理集合都会更加成问题,因为任何容量更改都会触发整个集合的重建,从而可以对元素进行重新排序。考虑以碰撞发生的顺序存储的散列的碰撞(变换的散列值相同的所有元素的简单链接链)。

另一答案

你插入后修改了套装吗?如果是这样,那么集合可能被分类到一个不同于它正在查看的桶中。当迭代时,它确实找到了你的集合,因为它在整个地图中查找。

我相信HashMap的合同声明你不允许修改用作键的对象的哈希码,

另一答案

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

以上是关于如何使用在Java Maps中设置为键的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Scala 中以元组为键合并 Maps

如何在iOS中设置为按钮选择的图像选择器图像

在java中设置为构造函数参数[重复]。

如何使用 XCTest 测试一个 navigationBar 是不是未在 Objective-C 中设置为特定颜色?

当用户的设备在 iOS 中设置为 24 小时制时,如何将包含“am/pm”的字符串转换为 NSDate?

如何使用在我的客户的 iTunes Connect 中设置为 App Manager 角色的帐户将 iOS 构建上传到 App Store?