Why ConcurrentModificationException occurred only at iterate loop

1k views Asked by At

I wrote two example code as following:

private static class Person
{
    String name;

    public Person(String name)
    {
        this.name = name;
    }
}
public static void main(String[] args)
{
    List<Person> l = new ArrayList<Person>();
    l.add(new Person("a"));
    l.add(new Person("b"));
    l.add(new Person("c"));
    l.add(new Person("d"));


    for(int i  = 0;i<l.size();i++)
    {
        Person s = l.get(i);
        System.out.println(s.name);
        if(s.name.equals("b"))
            l.remove(i);
    }
    System.out.println("==========================");

    for(Person s : l)
        System.out.println(s.name);
}

When I run example code, print on console following result:

a
b
d
==========================
a
c
d

but when I change code as following by a iterate model:

  int i  = 0;
  for(Person s : l)
  {
      if(s.name.equals("b"))
          l.remove(i);
      i++;
  }

I get following result:

a
b
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)

Means of examples are: In traditional loop model ConcurrentModificationException not occurred at traditional for model??

3

There are 3 answers

0
Jon Skeet On BEST ANSWER

Yes, in your original for loop you're accessing the value by index - although you should note that you're skipping the check for element c (because by then the value that was at index 2 is now at index 1). There's no way this would create a ConcurrentModificationException, as there's no "context" between accesses - there's nothing to be invalidated when you modify the list.

In the enhanced-for-loop version, you're using an iterator which keeps the context of where you are within the collection. Calling l.remove invalidates that context, hence the error.

You can avoid it by calling remove on the iterator itself:

for (Iterator<Person> iterator = l.iterator(); iterator.hasNext(); ) {
    Person s = iterator.next();
    if (s.name.equals("b")) {
        iterator.remove();
    }
}
0
JB Nizet On

Because the foreach loop actually uses an iterator. It's equivalent to

for(Iterator<Person> it; it.hasNext(); ) {
    Person s = it.next();
    if(s.name.equals("b"))
        l.remove(i);
    i++;
}

And the iterators of standard collections like ArrayList, LinkedList, etc. fail when they detect that the collection has been modified between two calls to next().

The first loop doesn't use any iterator. It directly asks the list to get its nth element. This is fine for an ArrayList, but is extremely slow for a LinkedList, which has to traverse half of the list at every iteration, making the iteration O(n^2) instead of O(n).

0
Thorsten Schöning On

Your second example hides an internally used iterator and if such an iteration is in place, you need to remove using the concrete iterator, not using the "global" remove method. In your first example you don't create an iterator, your are just accessing individual elemnts by some index, which is nothing the list cares about. But it does care about created, used, active iterators.

List.iterator Iterator.remove