迭代EnumMap#entrySet

枚举Map#entrySet对于所有Map实现都没有预期的效果,特别是对于EnumMap, IdentityHashMap ,这里是来自Josh Bloch的益智游戏(Puzzle 5)的示例代码 –

 public class Size { private enum Sex { MALE, FEMALE } public static void main(String[] args) { printSize(new HashMap()); printSize(new EnumMap(Sex.class)); } private static void printSize(Map 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> set = new HashSet<Map.Entry>(map.entrySet()); System.out.println(set.size()); } } 

是的,这会产生错误的结果 –

应该是

  2 2 

但产生

 2 1 

但如果我尝试下面的代码 – 它会产生正确的结果

UPDATE
虽然生成的Set的大小是2,但条目是相同的。

 public class Test{ private enum Sex { MALE, FEMALE } public static void main(String... args){ printSize(new HashMap()); printSize(new EnumMap(Sex.class)); } private static void printSize(Map map) { map.put(Sex.MALE, "1"); map.put(Sex.FEMALE, "2"); map.put(Sex.MALE, "3"); map.put(Sex.FEMALE, "4"); Set<Map.Entry> set = new HashSet<Map.Entry>(map.entrySet()); System.out.println(set.size()); } } 

我甚至用两种不同的枚举类型作为键和值来尝试上面的代码。

仅当EnumMap具有与键和值相同的枚举时,这似乎才是问题。

我想知道为什么会这样? 或者我错过了什么。为什么当ConcurrentHashMap长期修复时它没有修复?

看看EnumMap.EntryIterator.next()实现。 这应该足以找出问题所在。

一个线索是结果集是:

 [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 。 这导致Entry的哈希值相同。

如果条目具有相同的散列值,则它们最终位于散列表的同一个桶中,并且等于将始终起作用,因为它们无论如何都是相同的对象。

有了这些知识,我们现在也可以与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 enumMap = new EnumMap(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引用。 您可以按如下方式validation它:

 Iterator> e = map.entrySet().iterator(); while (e.hasNext()) { Map.Entry x = e.next(); System.out.println(System.identityHashCode(x)); } 

问题不在于地图,而在于EntryIterator和HashSet规范的实现只接受不相等的元素。

如果1和2映射应该有两个元素,您可以validation调用

 map.entrySet().size(); 

‘问题’在EnumMap类的EntryIterator实现中,因为这是一个谜题,试图找出自己的原因。

PS。 使用调试器。

编辑:

这是你真正做的是:

  Set> set = new HashSet>(); Iterator> e = entrySet.iterator(); while (e.hasNext()) { set.add(e.next()); } 

请记住,HashSet是通过HashMap实现的,这些值是基于哈希码和相等性添加到hashMap的。

BTW中解释的一切都与拼图有关。 该错误是在方法相同的情况下,在第二次调用方法next()之后,改变工作方式并比较类类型而不是值return o == this;