Java ConcurrentModificationException
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.
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.
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.