首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在Java Maps中使用集合作为键

如何在Java Maps中使用集合作为键
EN

Stack Overflow用户
提问于 2010-03-07 01:00:36
回答 4查看 25K关注 0票数 19

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

代码语言:javascript
运行
复制
Map<Set<Thing>, Val> map;

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

代码语言:javascript
运行
复制
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的关键字有什么秘密吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-03-07 03:04:04

在映射中使用时,键不应发生变化。Map java文档中写道:

注意:如果将可变对象用作映射键,则必须非常小心。当对象是映射中的键时,如果对象的值以影响等于比较的方式更改,则不指定映射的行为。此禁令的一个特例是,不允许映射将其自身包含为关键字。虽然允许映射将其自身包含为值,但建议您格外小心: equals和hashCode方法在这样的映射上不再定义良好。

我知道这个问题,但直到现在才进行测试。然后我再详细阐述一下:

代码语言:javascript
运行
复制
   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文档所说,键不应该改变,否则行为将是未指定的。句号。

我想这就是你的情况。

票数 24
EN

Stack Overflow用户

发布于 2010-03-07 01:27:29

你应该努力使用不可变的类型作为Map的键。集合和集合通常是非常容易改变的,所以使用这种方式通常不是一个好主意。

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

如果您确实必须使用Set或Collection作为键,请首先将其设置为不可变(Collections.unmodifiableSet(...)),然后不要保留对可变支持对象的引用。

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

编辑:我纠正了下面这句话,我从来没有用过Set for ket。我刚刚用AbstractHashSet阅读了hashCode实现的一部分。它使用所有值的简单总和,因此不依赖于顺序。Equals还检查一个集合是否包含另一个集合中的所有值。然而,这仍然适用于Java语言中的其他类型的集合(ArrayList顺序确实很重要)。

如果您的集合实际上是一个HashSet,那么创建顺序也很重要。事实上,任何类型的散列管理的集合都会有更大的问题,因为任何容量的改变都会触发整个集合的重新构建,这可能会重新排序元素。考虑按冲突发生的顺序存储的散列的冲突(转换后的散列值相同的所有元素的简单链接链)。

票数 8
EN

Stack Overflow用户

发布于 2010-03-07 01:06:54

您是否在插入后修改了集合?如果是这样的话,很可能集合被排序到了与它正在查找的存储桶不同的存储桶中。在迭代时,它确实会找到您的集合,因为它会在整个map中查找。

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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2393296

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档