Working with Java Collections – element removal, for-each and Iterators

I am sure many of you would have come across the use case of having to remove entries from a collection into which you had previously added data. There could be a variety of use cases where this pops up, a very common one is the case of a cache clean up or a pool clean up.

In either of the two use cases above, entries would have been added / accessed when needed and typically programmers write clean up tasks that loop over such structures to evaluate a policy that they would have set at creation time. If the entry in the pool / cache meets the policy constraints its retained, if it doesn’t its evicted. Sounds a simple enough problem to solve.

The advent of for-each loops structures in Java 5 came as a blessing, especially considering the ugly iterator code that people had to write before. The readability and maintainability definitely increased. But is it a good idea to replace all iterator code with the for-each loop ?

The answer is no. There are a few cases where it cannot and should not be replaced. In some cases the ramifications are visible and obvious, in other cases it is not as obvious.

DISCLAIMER : It may not be the best way to write code, but is only for demonstration of the use case ūüôā

To illustrate one such use case, take a look at the code snippets below,

    public void populateCollection()
    {
        for (int i = 0 ; i < 20 ; i++)
        {
            listOfStrings.add("String-"+i);
        }
        System.out.println("Size of the collection is: "+listOfStrings.size());
    }

The above code snippets, adds elements to an ArrayList.

The below snippet tries to remove the 10th element from the list while iterating over it.

public void removeItem()
    {
        // Remove item number 10
        // Expect it to work ?
        int counter = 0;
        try {
            for (String entry : listOfStrings) {
                System.out.println("Entry number " + (counter + 1) + " is : "
                        + entry);
                if (counter == 9) {
                    System.out.println("Removing Entry : " + entry);

                    listOfStrings.remove(entry);

                }
                counter++;
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("Oops did not expect this");
            e.printStackTrace();
        }
    }

Will this work ? Why not would be many people’s reaction.¬† Programmers may think why is this sample code even trying to catch a runtime exception? Again, this is to demonstrate that this exception is indeed thrown !!!

Surprised? You really should not be, because the whole problem is, there is an implicit Iterator here that is traversing the collection and you are going a removing an entry from the collection via a backdoor channel. Since these kind of collections have what are called fail-fast iterators, you will get a ConcurrentModificationException like below.

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at CollectionTester.removeItem(CollectionTester.java:XXX)

How do you solve this? Use the good old iterator traversal  and remove it using the Iterator, like below.

public void removeProperly()
    {
        int counter = 0;
        
        Iterator<String> listItr = listOfStrings.iterator();
        while(listItr.hasNext())
        {
            String entry = listItr.next();
            System.out.println("Entry number "+(counter+1) +" is : "+entry);
            if(counter == 9)
            {
                System.out.println("Removing Entry Properly : "+entry);
                try
                {
                    listItr.remove();
                }
                catch (ConcurrentModificationException e)
                {
                    System.out.println("Oops did not expect this");
                    e.printStackTrace();
                }
            }
            counter++;
        }
    }

This will work as expected and remove the entry. Having a try – catch block just to ensure everything is fine. This demonstrates a pitfall that programmers should avoid while replacing iterators with the for-each loop construct.

This has one more caveat though, the concurrent collections in the java.util.concurrent package have something called a weakly-consistent iterator whose definition is like this, ” iterator that will never throw¬† ConcurrentModificationException and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction”. So its a call that a programmer has to take while dealing with these collections, but it is probably better to stick with the usual iterator way.

Hope this entry helps people who have been hit by nasty, unexpcted ConcurrentModificationException  that is giving them nightmares !!!

Advertisements