迭代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 extends Map.Entry> 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;
。