问题描述
|
对于所有Map实现,枚举ѭ0all并不能按预期工作,尤其是EnumMap
IdentityHashMap
枚举,这是Josh Bloch的拼图游戏演示文稿中的示例代码(难题5)-
public class Size {
private enum Sex { MALE,FEMALE }
public static void main(String[] args) {
printSize(new HashMap<Sex,Sex>());
printSize(new EnumMap<Sex,Sex>(Sex.class));
}
private static void printSize(Map<Sex,Sex> map) {
map.put(Sex.MALE,Sex.FEMALE);
map.put(Sex.FEMALE,Sex.MALE);
map.put(Sex.MALE,Sex.MALE);
map.put(Sex.FEMALE,Sex.FEMALE);
Set<Map.Entry<Sex,Sex>> set =
new HashSet<Map.Entry<Sex,Sex>>(map.entrySet());
System.out.println(set.size());
}
}
是的,会产生错误的结果-
应该是
2
2
但产生
2
1
但是如果我尝试下面的代码-它会产生正确的结果
更新
尽管生成的Set的大小为2,但条目数相同。
public class Test{
private enum Sex { MALE,FEMALE }
public static void main(String... args){
printSize(new HashMap<Sex,String>());
printSize(new EnumMap<Sex,String>(Sex.class));
}
private static void printSize(Map<Sex,String> map) {
map.put(Sex.MALE,\"1\");
map.put(Sex.FEMALE,\"2\");
map.put(Sex.MALE,\"3\");
map.put(Sex.FEMALE,\"4\");
Set<Map.Entry<Sex,String>> set =
new HashSet<Map.Entry<Sex,String>>(map.entrySet());
System.out.println(set.size());
}
}
我什至尝试了上述代码,并使用了两种不同的枚举类型作为键和值。
仅当EnumMap具有与键和值相同的枚举时,这才似乎是问题。
我想知道为什么吗?还是我丢失了某些东西。为什么当ConcurrentHashMap早已修复时,它还是不固定?
解决方法
看一下“ 6”实现。这应该足以找出问题所在。
一个线索是结果集是:
[FEMALE=2,FEMALE=2]
这不是正确的结果。
您看到的效果归功于EnumMap.EntryIterator.hashCode()
实现(此处为Map.Entry)。它的
h = key ^ value
这将导致由产生的条目具有相同的哈希值
map.put(Sex.MALE,Sex.MALE);
map.put(Sex.FEMALE,Sex.FEMALE);
一个稳定的0。
要么
map.put(Sex.MALE,Sex.FEMALE);
map.put(Sex.FEMALE,Sex.MALE);
这是一个不稳定的(用于多次执行)int值。如果键和值哈希是相同的值,您将始终看到效果,因为:a ^ b == b ^ a
。这将导致条目具有相同的哈希值。
如果条目具有相同的哈希值,则它们将最终出现在哈希表的同一存储桶中,并且无论如何它们都是相同的对象,因此始终将起作用。
有了这些知识,我们现在还可以对其他类型(如Integer,我们知道hashCode实现)产生相同的效果:
map.put(Sex.MALE,Integer.valueOf(Sex.MALE.hashCode()));
map.put(Sex.FEMALE,Integer.valueOf(Sex.MALE.hashCode()));
[FEMALE=1671711,FEMALE=1671711]
奖励:EnumMap实现打破了equals()合同:
EnumMap<Sex,Object> enumMap = new EnumMap<Sex,Object>(Sex.class);
enumMap.put(Sex.MALE,\"1\");
enumMap.entrySet().iterator().next().equals(enumMap.entrySet().iterator());
抛出:
Exception in thread \"main\" java.lang.IllegalStateException: Entry was removed
at java.util.EnumMap$EntryIterator.checkLastReturnedIndexForEntryUse(EnumMap.java:601)
at java.util.EnumMap$EntryIterator.getValue(EnumMap.java:557)
at java.util.EnumMap$EntryIterator.equals(EnumMap.java:576)
at com.Test.main(Test.java:13)
, EnumMap.EntryIterator.next()
返回this
参考。您可以按以下方式进行验证:
Iterator<? extends Map.Entry<Sex,Sex>> e = map.entrySet().iterator();
while (e.hasNext()) {
Map.Entry<Sex,Sex> x = e.next();
System.out.println(System.identityHashCode(x));
}
, 问题不在于映射,而在于EntryIterator
的实现和仅接受不相等元素的HashSet规范。
如果1和2映射应包含两个元素,则可以验证调用
map.entrySet().size();
问题“”在EnumMap类的EntryIterator的实现中,因为试图弄清楚为什么是一个难题。
ps。使用调试器。
编辑:
这是您真正在做的事情:
Set<Map.Entry<Sex,Sex>> set = new HashSet<Map.Entry<Sex,Sex>>();
Iterator<Map.Entry<Sex,Sex>> e = entrySet.iterator();
while (e.hasNext()) {
set.add(e.next());
}
请记住,HashSet是在HashMap上实现的,HashMap是基于哈希码和相等性添加到hashMap的值。
BTW OP中解释的所有内容均链接至该难题。错误在于equal方法中,该方法在第二次调用方法next()之后,更改工作方式并比较类类型而不是值return o == this;
。