If I ever forget that there are cases where the enhanced for loop does not apply, I am quickly reminded when I use it in such cases. The three cases in which the enhanced for loop is not allowable are in iterating over parallel collections, in trying to change values in the collection as you iterate, and in removing elements from the collection you are iterating. The last usage is the subject of this blog entry.
The Collections Framework documentation makes it very clear that items can only be safely removed from an iterated collection via the Iterator's remove method. Because the enhanced for loop hides the Iterator (making the code so much cleaner and more concise), removal is not an option with this loop style.
The following code snippet demonstrates how to safely remove an element from a Java Map and also demonstrates how NOT to do this with commented out code that leads to different exceptions (ConcurrentModificationException and IllegalStateException).
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
public class LoopingFun
{
private static Map<String, String> translatorMap = new HashMap<String,String>();
static
{
translatorMap.put("zero", "cero");
translatorMap.put("one", "uno");
translatorMap.put("two", "dos");
translatorMap.put("three", "tres");
translatorMap.put("four", "cuatro");
translatorMap.put("five", "cinco");
translatorMap.put("six", "seis");
translatorMap.put("seven", "siete");
translatorMap.put("eight", "ocho");
translatorMap.put("nine", "nueve");
translatorMap.put("ten", "diez");
}
public static void main(final String[] arguments)
{
System.out.println("Pre-Size: " + translatorMap.size());
for ( final String translatorKey : translatorMap.keySet() )
{
// Line below is commented out to avoid ConcurrentModificationException
// (cannot remove directly on the Map while iterating; must remove from
// the Iterator, but the enhanced for-each loop "hides" the Iterator).
//translatorMap.remove(translatorKey);
}
final Iterator mapIter = translatorMap.keySet().iterator();
while ( mapIter.hasNext() )
{
// Line below is commented out to avoid IllegalStateException (need to
// call Iterator's hasNext() method first.
//mapIter.remove();
// Line below is commented out to avoid ConcurrentModificationException
// (cannot remove directly on the Map while iterating; must remove from
// the Iterator).
//translatorMap.remove((String)mapIter.next());
mapIter.next();
mapIter.remove();
}
System.out.println("Post-Size: " + translatorMap.size());
}
}
When the code exists as shown above (working with problem lines commented out), the results of running the compiled class look like this:
The next three snapshots show what the runtime output looks like when the appropriate code is commented out and the three commented out examples in the code above are each individually uncommented. Note that these are runtime errors rather than compile time errors.
Because the for-each loop hides the Iterator, one might tempted to call the
remove
method directly on the Map
. When this is done, a ConcurrentModificationException
occurs as shown in the next screen snapshot. Note that the line cited in the trace is the line of the for loop rather than the line where the remove()
is actually called.If one tries to call a removal in a more traditional explicit Iterator-driven iteration without first calling
Iterator.next()
, an IllegalStateException
is thrown as shown in the next screen snapshot.Finally, the last "don't do it this way" example shows trying to call
remove
directly on the Map
rather than on the Iterator
. Even though this third example of Collection removal abuse is included in a traditional explicit Iterator
-driven iteration loop, it still results in a ConcurrentModificationException
because only an Iterator
(and not the Collection itself) allows a safe removal during iteration. The final screen snapshot indicates this exception, which in this case shows the line of the remove()
call in the trace.The enhanced for-each loop in Java is a sweet piece of syntactic sugar. It can be used for many of the common, everyday Java Collection iteration/looping needs. However, as this blog entry demonstrated, there are times when it is not appropriate and the more traditional and explicit
Iterator
-driven iteration must be used instead.
1 comment:
Good and useful info. Thanks for sharing :)
Post a Comment