Background
Given an ArrayList of Integers from 0 (inclusive) to 10 (exclusive) with
step size 1. We use a while-loop and an Iterator<Integer> to check if
this ArrayList hasNext() element, before we remove() the current element.
Problem
The code below throws an IllegalStateException.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class UnderstandArrayListIterator {
public static void main(String[] args) {
List<Integer> a = new ArrayList<>();
for (int i = 0; i < 10; i++) {
a.add(i);
}
Iterator<Integer> iter = a.iterator();
iter.next();
while (iter.hasNext()) {
iter.remove();
}
}
}
Discussion
After invoking remove(), lastRet is set to -1, so
next time that this method is invoked, the condition lastRet < 0 in the
if-block is satisfied, resulting in an IllegalStateException.
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
Things learnt
ArrayList<E>uses a private inner classItr implements Iterator<E>, in whichlastRetcan be regarded as the “current element”cursoris actually a key to retrive the next element returned bynext().expectedModCountrecordsmodCountwhen this iterator is instantiated.
ArrayList<E>uses a transient class variablemodCountto store the number of times that thisArrayList<E>has been structually modified (i.e. the number of times that thisArrayList<E>’s size has been changed) since thisArrayList<E>has been instantiated.- a
transientclass member doesn’t reflect the state of an object, so it’s not included in the serialized object. For example, here’re two ways to get a same state while clearing anArrayList<E>of multiple objects:clear()all elements, leading to an increment ofmodCountby1.remove(int index)/remove(Object o)elements one-by-one, leading to a total increment ofmodCountthat equals to theArrayList<E>’s size.
clone()resetsmodCountto0since the cloned list has another return typeObject, i.e. another object.- Java Point provides an example of concurrent modification
that has a
list({1, 2, 3, 4, 5}) and itsiterator. Inside awhile(it.hasNext())-loop,- each value in
listis compared to3. - when a matching occurs (i.e.
it.next()returned3), remove3from the list bylist.remove(3), for the purpose of reproducing aConcurrentModificationException, so thatmodCountincrements by1. Since this instruction doesn’t involveit, andmodCountis of primitive type,expectedModCountdoesn’t change. - a
ConcurrentModificationExceptionshould be produced, since such deletion introduces a left-shifting of the “tail” (i.e. all elements that comes after the deleted element. ({4, 5})),it’s fieldcursor(that stores the index of the element4next to the deleted element3) is greater than what we expect by1. That’s not a usual use case, so such exception is thrown to stop the process from running. - in the next iteration, the
next()method is invoked. In this method,checkForComodification()is invoked. This name ofcheckForComodificationis self-explanatory. Inside thefinal voidmethodcheckForComodification(), it does one simple thing: checkif (modCount != expectedModCount), andthrow new ConcurrentModificationException()if the actually modification count is different from what we expected. In this example, the condition in thisif-block is verified for the reason explained in point no. 2, so aConcurrentModificationExceptionis thrown. - If we’re modifying this
listnormally (throughit.remove()), we shouldn’t get this error because in thetry-block of the inner class methodItr#remove(), there’s a value assignmentexpectedModCount = modCount. - remarks for this example: IMHO, using alphabets instead of digits for
the
list’s entries is much less confusing, since my proposed entry typecharcan’t be confused with the index’s typeint.
- each value in
- a
Collection<E>#remove(Object o)accepts whatever object, but it throwsClassCastExceptionifocan’t be casted into typeE.NullPointerExceptionifoisnulland this collection doesn’t acceptnullentry value.
ArrayList<E>#clone()returns a shallow copy of thisArrayList<E>, i.e. a different instace ofArrayList<E>in which every element is copied by reference.