FULL PRODUCT VERSION :
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
The entries returned by the Iterator of EntryMap.entrySet() DO NOT preserve values during iteration.
If Iterator.next() is called then the Entry obtained by the previous call to next() will unexpectedly change its key and value to the "next" element in the EnumMap.
This contradicts with http://download.oracle.com/javase/6/docs/api/java/util/EnumMap.html#entrySet() which says that entrySet() obeys the general contract of Map.keySet()
and
http://download.oracle.com/javase/6/docs/api/java/util/Map.Entry.html which says " Map.Entry objects are valid only for the duration of the iteration; more formally, the behavior of a map entry is undefined if the backing map has been modified after the entry was returned by the iterator"
The actual behavior is that the key and value inside the Entry are changed implicitly DURING the iteration (while the backing map is NOT modified)
The Set<Map.Entry> returned by the HashMap.entrySet() does not have this issue
The cause of the issue is that java.util.EnumMap.EntryIterator.next() returns the iterator itself. The javadoc for the class EntryIterator simply says "Since we don't use Entry objects, we use the Iterator itself as entry" so it's hard to understand why such implementation has been taken.
The solutions suggested by me are:
- (Preferrable) next() should create the freshly created Entry object instead of the iterator
- The javadocs of the EnumMap are updated to explicitly say that Entry returned by next() method will be implicitly modified by subsequent next() calls
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the program from the test case (see below)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected output of the program should be:
FIRST : first
FIRST : first
SECOND : second
SECOND : second
ACTUAL -
The actual output of the program is
FIRST : first
SECOND : second
SECOND : second
SECOND : second
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package org.volchyn.example;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public enum SampleEnum {
FIRST,
SECOND,
THIRD;
public static void main(String[] args) {
/* Use EnumMap to add to mappings and then iterate through this map */
EnumMap<SampleEnum, String> enumMap = new EnumMap<SampleEnum, String>(
SampleEnum.class);
enumMap.put(FIRST, "first");
enumMap.put(SECOND, "second");
Set<Entry<SampleEnum, String>> enumEntrySet = enumMap.entrySet();
Iterator<Entry<SampleEnum, String>> enumIterator = enumEntrySet.iterator();
Entry<SampleEnum, String> enumEntry = enumIterator.next();
System.out.println(enumEntry.getKey() + " : " + enumEntry.getValue());
/* Continue iteration */
enumIterator.next();
/* The key and value inside the entry have been changed unexpectedly */
System.out.println(enumEntry.getKey() + " : " + enumEntry.getValue());
/* Execute the very same example using HashMap */
HashMap<SampleEnum, String> hashMap = new HashMap<SampleEnum, String>();
hashMap.put(FIRST, "first");
hashMap.put(SECOND, "second");
Set<Entry<SampleEnum, String>> hashEntrySet = hashMap.entrySet();
Iterator<Entry<SampleEnum, String>> hashIterator = hashEntrySet.iterator();
Entry<SampleEnum, String> hashEntry = hashIterator.next();
System.out.println(hashEntry.getKey() + " : " + hashEntry.getValue());
/* Continue iteration */
hashIterator.next();
/* The key and value inside the entry are preserverd */
System.out.println(hashEntry.getKey() + " : " + hashEntry.getValue());
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The workaround that I used is to store the actual key and value of the Entry before calling Iterator.next()