迭代通过EnumMap#entrySet [英] Iterating over EnumMap#entrySet
问题描述
枚举 Map#entrySet
无法像所有Map实现一样正常工作,特别是对于EnumMap, IdentityHashMap
这里是来自Josh Bloch的拼音演示文稿的示例代码(Puzzle 5) -
public class Size {
私人枚举性{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
但如果我尝试使用下面的代码 -
result
UPDATE
虽然结果集的大小为2,但条目相同。public class Test {
私人列举性别{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固定回来的时候不是固定的?
解决方案c $ c> EnumMap.EntryIterator.next()实现。这应该足以找出问题。
一个线索是,结果集是:
[FEMALE = 2,FEMALE = 2]
正确的结果。
您看到的效果是由于
EnumMap.EntryIterator.hashCode()
实现这里Map.Entry)。h = key ^ value
这会导致
产生的条目具有相同的哈希值。
map.put .MALE,Sex.MALE);
map.put(Sex.FEMALE,Sex.FEMALE);
a stable 0。
/ p>
map.put(Sex.MALE,Sex.FEMALE);
map.put(Sex.FEMALE,Sex.MALE);
这里是一个不稳定的如果键和值的哈希值是相同的值,你总会看到效果,因为:
a ^ b == b ^ a
。这将导致条目的散列值相同。
如果条目具有相同的散列值,那么它们最终会在散列表的同一个桶中,而equals将总是起作用因为它们是相同的对象。
有了这个知识,我们现在可以产生与其他类型像Integer(我们知道hashCode实现)相同的效果: p>
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());
抛出:
code>线程main中的异常java.lang.IllegalStateException:删除条目
在java.util.EnumMap $ EntryIterator.checkLastReturnedIndexForEntryUse(EnumMap.java:601)
在java.util。 EnumMap $ EntryIterator.getValue(EnumMap.java:557)
在java.util.EnumMap $ EntryIterator.equals(EnumMap.java:576)
at com.Test.main(Test.java:13)
Enumerating over
Map#entrySet
doesn't work as expected for all Map implementations, specially for EnumMap,IdentityHashMap
and here is the sample code from Josh Bloch's 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()); } }
and yes that produces the wrong result -
supposed to be
2 2
but produces
2 1
but if I try with below code -
it produces the correct resultUPDATE
Though the size of the resulting Set is 2 but Entries are same.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()); } }
I even tried the above code with the two different enum types as key and value.
This seems to be issue only if EnumMap has a same enum as a key and value.I would like to know why is this? or I'm missing something.why it's not fixed when ConcurrentHashMap got fixed long back?
解决方案Have a look at the
EnumMap.EntryIterator.next()
implementation. This should be enough to figure out the problem.A clue is that the resulting set is:
[FEMALE=2, FEMALE=2]
which is not the correct result.
The effect you see is due to the
EnumMap.EntryIterator.hashCode()
implementation (which is the Map.Entry here). It'sh = key ^ value
This results in the same hash value for the entries produced by
map.put(Sex.MALE, Sex.MALE); map.put(Sex.FEMALE, Sex.FEMALE);
a stable 0.
or
map.put(Sex.MALE, Sex.FEMALE); map.put(Sex.FEMALE, Sex.MALE);
here it's an instable (for multiple executions) int value. You will always see the effect if key and value hashs are the same value because:
a ^ b == b ^ a
. This results in the same hash value for the Entry.If entries have the same hash value they end up in the same bucket of the hash table and the equals will always work as they are the same object anyway.
With this knowledge we can now also produce the same effect with other types like Integer (where we know the hashCode implementation):
map.put(Sex.MALE, Integer.valueOf(Sex.MALE.hashCode())); map.put(Sex.FEMALE, Integer.valueOf(Sex.MALE.hashCode())); [FEMALE=1671711, FEMALE=1671711]
Bonus: The EnumMap implementation breaks the equals() contract:
EnumMap<Sex, Object> enumMap = new EnumMap<Sex, Object>(Sex.class); enumMap.put(Sex.MALE, "1"); enumMap.entrySet().iterator().next().equals(enumMap.entrySet().iterator());
Throws:
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#entrySet的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!