Enumerating over Map#entrySet
doesn\'t work as expected for all Map implementations, specially for EnumMap, IdentityHashMap
and here is the sample code
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's
h = 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 enumMap = new EnumMap(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)