Unspecified behavior of the Java Iterator

In this article we will take a look at how the behavior of the Iterator produces some surprising results if not used in the right away.

In my previous article, we saw different ways of  removing elements from an ArrayList. When we used the remove(Object o) method from the ArrayList while we traverse through the ArrayList, we get a ConcurrentModificationException. But is this always the case ?

Well, let’s consider the same example of removing a person below the age of 18 from an ArrayList but with a different data set.


package blog;
import java.util.ArrayList;
import java.util.List;
public class RemoveItemsEnhancedForModified {
private static List<Person> persons = new ArrayList<>();
private static void initialize() {
persons.add(new Person("Rahul", 25, Gender.MALE, Citizenship.INDIA));
persons.add(new Person("Sally", 21, Gender.FEMALE, Citizenship.USA));
persons.add(new Person("Ben", 35, Gender.MALE, Citizenship.CANADA));
persons.add(new Person("Wayne", 30, Gender.MALE, Citizenship.UK));
persons.add(new Person("ShaneYoung", 18, Gender.MALE, Citizenship.AUSTRALIA));
persons.add(new Person("Kimmo", 17, Gender.MALE, Citizenship.FINLAND));
persons.add(new Person("Simo", 17, Gender.MALE, Citizenship.FINLAND));
}
public static void main(String[] args) {
initialize();
removeIllegalUsingEnhancedForLoop();
System.out.println(persons);
}
private static void removeIllegalUsingEnhancedForLoop()
{
for (Person p : persons)
{
if (p.getAge() < 18)
{
persons.remove(p);
}
}
}
}

Most of the code remains the same, the only thing that has changed is the data. Notice the Person data on the 2nd last and last elements in the ArrayList.

persons.add(new Person("Kimmo", 17, Gender.MALE, Citizenship.FINLAND));
persons.add(new Person("Simo", 17, Gender.MALE, Citizenship.FINLAND));

Well, anyways, we should get a ConcurrentModificationException! Right ?

On running this the output :

[Rahul, Sally, Ben, Wayne, ShaneYoung, Simo]

Shocked ? No exception ! Not just that , Simo should have been filtered. Well, lucky Simo! He can still have beer even though he is 17.

So what is happening  ?  Why is an exception not thrown and why is the last element returned ?

The 2nd last element satisfies the filtering criteria , it gets deleted. When it gets deleted, the size of the ArrayList is reduced by 1. But, the combination of Iterating and calling remove(Object o) makes the Iterator to behave in a weird way.  When remove(Object o) is called, it makes a call to the fastRemove method which reduces the size of the list. Then there is a call to the hasNext() on the iterator to check if there are more elements to to traversed. The hasNext() looks as follows:

public boolean hasNext() {
    return cursor != size;
 }

The cursor is an index that points to the index of the next element that will be processed. It starts from 0. In our case when we delete the 2nd last element, the cursor value is 6.

Before deleting: (size =7 , cursor =6 at Simo)

Rahul
21
Sally
21
Ben
35
Wayne
30
ShaneYoung
30
Kimmo
17
Simo
  17

After deleting: (size =6 , cursor = 6 at Simo)

Rahul
21
Sally
21
Ben
35
Wayne
30
ShaneYoung
30
Simo
17

Original value of size in our list is 7. When 2nd last element(Kimmo) is deleted, size =6 , cursor = 6. The Iterator thinks all the elements have been visited. So after deleting, when hasNext() is called ( remember that an enhanced for loop still means that the Iterator is used for moving around), the above condition returns false and hence the next() is not called. The last element is completely skipped and included in the output ! Since the hasNext returns false, next() is not called and the exception is not thrown. It is the call to next() which checks for concurrent modification but it is never called.

final void checkForComodification() {
     if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
 }

The Javadoc for Iterator remove method clearly states that the behavior of the iterator is unspecified if the underlying collection is modified while the iteration is in progress in any other way than by calling Iterator remove(). So the above behavior is pretty much unspecified but can catch you by surprise.

Conclusion

Read the Javadocs carefully!  I know you are religiously going to do that 🙂 !

If you are still using Java 7 or any version below that, don’t by pass the gate keeper, “The Iterator “, when simultaneously looping and deleting elements from an ArrayList. Use the remove() method from the Iterator.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: