Background
Given an ArrayList
of Integer
s 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 whichlastRet
can be regarded as the “current element”cursor
is actually a key to retrive the next element returned bynext()
.expectedModCount
recordsmodCount
when this iterator is instantiated.
ArrayList<E>
uses a transient class variablemodCount
to 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
transient
class 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 ofmodCount
by1
.remove(int index)
/remove(Object o)
elements one-by-one, leading to a total increment ofmodCount
that equals to theArrayList<E>
’s size.
clone()
resetsmodCount
to0
since 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 itsit
erator. Inside awhile(it.hasNext())
-loop,- each value in
list
is compared to3
. - when a matching occurs (i.e.
it.next()
returned3
), remove3
from the list bylist.remove(3)
, for the purpose of reproducing aConcurrentModificationException
, so thatmodCount
increments by1
. Since this instruction doesn’t involveit
, andmodCount
is of primitive type,expectedModCount
doesn’t change. - a
ConcurrentModificationException
should 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 element4
next 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 ofcheckForComodification
is self-explanatory. Inside thefinal void
methodcheckForComodification()
, 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 aConcurrentModificationException
is thrown. - If we’re modifying this
list
normally (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 typechar
can’t be confused with the index’s typeint
.
- each value in
- a
Collection<E>#remove(Object o)
accepts whatever object, but it throwsClassCastException
ifo
can’t be casted into typeE
.NullPointerException
ifo
isnull
and this collection doesn’t acceptnull
entry 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.