【问题标题】:Iterating over EnumMap#entrySet遍历 EnumMap#entrySet
【发布时间】:2011-06-01 08:18:03
【问题描述】:

枚举Map#entrySet 不能按预期对所有 Map 实现工作,特别是对于 EnumMap,IdentityHashMap,这是来自 Josh Bloch 的 puzzler presentation (Puzzle 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,但 Entries 是相同的。

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 很久以前修复时它没有修复?

【问题讨论】:

  • 我在 IntelliJ 中接收:2, 2

标签: java collections enums map


【解决方案1】:

看看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。这会导致条目的哈希值相同。

如果条目具有相同的哈希值,它们最终会在哈希表的同一个桶中,并且等号将始终有效,因为它们是同一个对象。

有了这些知识,我们现在也可以用其他类型产生相同的效果,比如 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)

【讨论】:

  • 这真的很丑!我想知道它是否符合规范! (顺便说一句:here is the source (starting at line 561))。
  • 同意.. 想知道当 concurrentHashMap 很久以前修复时它为什么没有修复?
  • 我认为接下来他应该着眼于equals实施。
  • @Joachim 不,它没有 - 查看奖金。
  • 要回答我自己的问题,它似乎已经在 J​​ava 7 中得到解决——这个确切的错误报告是 here 并作为 Java 已解决问题的副本(早在 2011 年 4 月)而关闭7 的发布。
【解决方案2】:

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));
}

【讨论】:

    【解决方案3】:

    问题不在于地图,而在于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 的值基于哈希码和相等性添加。

    顺便说一句,OP 链接中解释的所有内容都指向谜题。错误在于 equal 方法,在第二次调用方法 next() 后,更改工作方式并将类类型与值 return o == this; 进行比较。

    【讨论】:

    • 你在误导猎鹰。问题不在 HashSet 中。
    • 真正的问题是EntryIterator在Enum中的实现。
    猜你喜欢
    • 2018-04-08
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-05
    • 2018-07-15
    • 2014-01-23
    相关资源
    最近更新 更多