如何在 Java Maps 中使用 Sets 作为键
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2393296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use Sets as keys in Java Maps
提问by Gabe Johnson
I have a Map that uses a Set for the key type, like this:
我有一个 Map 使用 Set 作为键类型,如下所示:
Map<Set<Thing>, Val> map;
When I query map.containsKey(myBunchOfThings), it returns false, and I don't understand why. I can iterate through each key in the keyset and verify there is a key that (1) has the same hashCode, and (2) is equals() to myBunchOfThings.
当我查询 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.
}
}
Do I just fundamentally misunderstand the contract for containsKey? Is there a secret to using sets (or more generally, collections) as keys to maps?
我只是从根本上误解了 containsKey 的合同吗?使用集合(或更一般地说,集合)作为映射的键有什么秘密吗?
采纳答案by ewernli
Key should not be mutated while used in the map. The Map
java doc says:
在地图中使用时不应改变键。在Map
java的医生说:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on a such a map.
注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不会指定映射的行为。此禁令的一个特殊情况是不允许映射将自身包含为键。虽然允许映射将自身包含为值,但建议格外小心:equals 和 hashCode 方法不再在此类映射上明确定义。
I knew this issue, but never made the test until now. I elaborate then a bit more:
我知道这个问题,但直到现在才进行测试。我再详细说明一下:
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
After key2
is mutated, the map does not contain it anymore. We could think that the map "indexes" the data when it's added and we would then expect that it still contains the key2 clone (line marked with *
). But funny enough, this is not the case.
之后key2
发生突变时,地图不包含它了。我们可以认为地图在添加数据时“索引”了数据,然后我们期望它仍然包含 key2 克隆(标有 的行*
)。但有趣的是,事实并非如此。
So, as the java doc says, keys should not be mutated otherwise the behavior is unspecified. Period.
因此,正如 java 文档所说,不应改变键,否则行为是unspecified。时期。
I guess that's what happens in your case.
我想这就是你的情况。
回答by Jorn
Did you modify the set after insertion? If so, it's possible the set got sorted into a different bucket than the one it's looking in. When iterating, it does find your set, because it looks in the whole map.
插入后是否修改了集合?如果是这样,则该集合可能被分类到与其查找的桶不同的桶中。迭代时,它确实找到了您的集合,因为它在整个地图中查找。
I believe the contract for HashMap states you're not allowed to modify the hashcode for objects used as a key,
我相信 HashMap 的合同规定您不得修改用作键的对象的哈希码,
回答by Leniel Maccaferri
Are you passing the exact set (the set you want to find) when comparing for the key?
比较密钥时,您是否传递了确切的集合(您要查找的集合)?
回答by Kevin Brock
You should strive to use immutable types as keys for Map
s. Collections and sets are generally very easily mutable so usually are a bad idea to use this way.
您应该努力使用不可变类型作为Map
s 的键。集合和集合通常很容易可变,因此使用这种方式通常是一个坏主意。
If you want to use many key values as a Map
key you should use a class implementation designed for that purpose, like Apache Commons Collections MultiKey
.
如果您想使用多个键值作为Map
键,您应该使用专为此目的设计的类实现,例如 Apache Commons Collections MultiKey
。
If you really must use a Set or Collection as a key, first make it immutable (Collections.unmodifiableSet(...)
) and then do not keep a reference to the mutable backing object.
如果您确实必须使用 Set 或 Collection 作为键,请先使其不可变 ( Collections.unmodifiableSet(...)
),然后不要保留对可变支持对象的引用。
Another difficulty with using Collections as keys is that they could be constructed in a different order. Only a sorted collection will have a high likely-hood of matching. For instance, if you use a sequentially ordered ArrayList
but construct the list in a different way the second time it will not match the key - the hash code and order of values is different.
使用集合作为键的另一个困难是它们可以以不同的顺序构造。只有经过排序的集合才会有很高的匹配可能性。例如,如果您使用顺序排序ArrayList
但第二次以不同的方式构造列表,它将不匹配键 - 哈希码和值的顺序是不同的。
EDIT: I stand corrected on this statement below, never having had to use Set for a ket. I just read a portion of the hashCode implementation in AbstractHashSet. This uses a simple total of all values so is not dependent of the order. Equals also checks that one set contains all values in the other set. However, this still is true with other kinds of Collections in the Java (ArrayList order does matter).
编辑:我对下面的这个声明进行了更正,从来没有必要使用 Set 作为一个 ket。我刚刚阅读了 AbstractHashSet 中 hashCode 实现的一部分。这使用所有值的简单总计,因此不依赖于顺序。Equals 还会检查一组是否包含另一组中的所有值。但是,这对于 Java 中的其他类型的集合仍然是正确的(ArrayList 顺序很重要)。
If your collection is actually a HashSet
, the creation order can matter as well. In fact a hash managed collection of any kind will be even more problematic as any capacity changes trigger a rebuild of the entire collection which can reorder the elements. Think of collisions of hashes which are stored in the order the collision occur (a simple linked chain of all elements where the transformed hash value is the same).
如果您的集合实际上是 a HashSet
,则创建顺序也很重要。事实上,任何类型的散列管理集合将更加成问题,因为任何容量变化都会触发整个集合的重建,这可以重新排序元素。想想以冲突发生的顺序存储的哈希冲突(所有元素的简单链接链,其中转换后的哈希值相同)。