6

when remove the second last element there is no ConcurrentModificationException

List<String> myList1 = new ArrayList<String>();
Collections.addAll(myList1, "str1","str2","str3","str4","str5");
for(String element : myList1){//no ConcurrentModificationException here
if(element.equalsIgnoreCase("str4"))
    myList1.remove("str4");
}
System.out.println(myList1);

But when remove other elements there is a ConcurrentModificationException

List<String> myList2 = new ArrayList<String>();
Collections.addAll(myList2, "str1","str2","str3","str4","str5");
for(String element : myList2){//ConcurrentModificationException here
if(element.equalsIgnoreCase("str1"))
    myList2.remove("str1");
}
System.out.println(myList2);

what is the reason?

user1947415
  • 933
  • 4
  • 14
  • 31

4 Answers4

3

Java use a modCount(modification count) and an expectedCount to test whether there is a modification to the list.

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

In both condition, modCount is 6 after the remove, but expectedModCount is 5.

The problem is the hasNext().

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

The list use a cursor and size to check whether has a next element. And the hasNext() is happend before the checkForComodification because the checkForComodification() is called in the next() method.

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

So when you remove the second last element, the cursor=4, and size=4 also. hasNext() return false. Jump out of the loop and print the result.

StarPinkER
  • 14,081
  • 7
  • 55
  • 81
  • But, my point is, the only difference between two pieces of code is that the first one remove the "str4" while the second one remove "str1". But, the first one run successfully. The second one throw a ConcurrentModificationException. – user1947415 Feb 21 '13 at 04:09
  • This is exactly the reason why it failed when you removed second element. +1 Jermaine Xu – Anupam Saini Feb 21 '13 at 04:45
3

I'm seeing the same thing,

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Launcher 
{
    public static void main(String[] args) 
    {
        doThis();
        doThat();
    }

    private static void doThis()
    {
        System.out.println("dothis");
        try
        {
            List<String> myList1 = new ArrayList<String>();
            Collections.addAll(myList1, "str1","str2","str3","str4","str5");
            for(String element : myList1){//no ConcurrentModificationException here
            if(element.equalsIgnoreCase("str4"))
                myList1.remove("str4");
            }
            System.out.println(myList1);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    private static void doThat()
    {
        System.out.println("dothat");
        try
        {
            List<String> myList2 = new ArrayList<String>();
            Collections.addAll(myList2, "str1","str2","str3","str4","str5");
            for(String element : myList2){//ConcurrentModificationException here
            if(element.equalsIgnoreCase("str1"))
                myList2.remove("str1");
            }
            System.out.println(myList2);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

which outputs,

dothis
[str1, str2, str3, str5]
dothat
java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)
    at com.foo.Launcher.doThat(Launcher.java:41)
    at com.foo.Launcher.main(Launcher.java:12)

And I've found the reason.

Community
  • 1
  • 1
mre
  • 43,520
  • 33
  • 120
  • 170
2

The actual code that javac builds for for-each is

    Iterator<String> i = myList1.iterator();
    while(i.hasNext()) {
        String element = i.next();
        if (element.equalsIgnoreCase("str4"))
            myList1.remove("str4");
    }

and this is ArrayList Iterator.hasNext implementation

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

as we can see hasNext() does not check for concurrent modification so when we remove the last but one element the loop ends without noticing the the problem.

Actually it is strange that next() and remove() check for concurrent modification but hasNext() does not. Fail-fast iterator is supposed to detect bugs, but our bug went unnoticed.

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
-2

This is a commonly occurring issue. StackOverflow has hundreds of threads covering this. You can find the answer to your question here:

How can I iterate over an object while modifying it in Java?

When you remove the second last element, The hasNext() check fails and the loop iteration stops. Check the ArrayList iterator code in JDK.

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.Itr.hasNext%28%29

But in case of removal of second element the hasNext() check passes and you enter the next() method where the first thing it checks for is modification to the arrayList and hence the exception. Please check this code:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.Itr.next%28%29

The safest way is to remove the element using the iterators remove method.

Try debugger to step over the code, for a better understanding of how it works.

Community
  • 1
  • 1
Anupam Saini
  • 2,431
  • 2
  • 23
  • 30
  • 1
    You didn't get me guys. I understand I need to use Iterator to remove element while iterate through it. But, my question is why the first piece of code didn't throw a ConcurrentModificationException as expect. I have test this only happens when you remove the second last element. – user1947415 Feb 21 '13 at 04:14
  • I have updated my answer, To understand these sort of issues Debugger is your best bet – Anupam Saini Feb 21 '13 at 04:43