我有一个使用Set作为键类型的Map,如下所示:
Map<Set<Thing>, Val> map;
当我查询map.containsKey(myBunchOfThings)时,它返回false,我不明白为什么。我可以遍历键集中的每个键,并验证是否有一个键(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的合约?使用集合(或更一般地说,集合)作为地图的关键是否有秘密?
答案 0 :(得分:21)
在地图中使用时不应该改变键。 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所说,密钥不应该被改变,否则行为是未指定。周期。
我想这就是你的情况。
答案 1 :(得分:7)
您应该努力使用不可变类型作为Map
的键。集合和集合通常很容易变化,因此使用这种方式通常是个坏主意。
如果要将许多键值用作Map
键,则应使用为此目的设计的类实现,例如Apache Commons Collections MultiKey
。
如果你真的必须使用Set或Collection作为键,首先使它成为不可变的(Collections.unmodifiableSet(...)
),然后不要保留对可变后备对象的引用。
使用Collections作为键的另一个困难是它们可以以不同的顺序构造。只有排序的集合才会有很高的匹配率。例如,如果您使用顺序排序的ArrayList
但是以不同的方式构造列表,那么第二次它将与键不匹配 - 哈希码和值的顺序是不同的。
编辑:我在下面的声明中做了更正,从来没有必要使用Set for a ket。我刚刚在AbstractHashSet中读取了一部分hashCode实现。这使用了所有值的简单总和,因此不依赖于顺序。 Equals还会检查一组是否包含另一组中的所有值。但是,Java中的其他类型的集合仍然如此(ArrayList顺序很重要)。
如果您的收藏集实际上是HashSet
,则创建顺序也很重要。实际上,任何类型的哈希管理集合都会更加成问题,因为任何容量更改都会触发整个集合的重建,从而可以对元素进行重新排序。想象一下以冲突发生的顺序存储的哈希冲突(所有元素的简单链接链,其中变换的哈希值相同)。
答案 2 :(得分:2)
插入后是否修改了设置?如果是这样,那么集合可能被分类到一个不同于它正在查看的桶中。当迭代时,它确实找到了你的集合,因为它在整个地图中查找。
我相信HashMap的合同声明你不允许修改用作密钥的对象的哈希码,
答案 3 :(得分:0)
在比较密钥时,您是否传递了确切的集合(您要查找的集合)?