遍历EnumMap#entrySet

问题描述

| 对于所有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;
。