ArrayList、LinkedList、HashMap中都有一個字段叫modCount。這個字段的用途,在ArrayList的父類AbstractList源碼中有注釋,說的很清楚:
/**
* The number of times this list has been <i>structurally modified</i>.Structural modifications are those that change the size of the list,
* or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous}, {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be ignored.
*/
protected transient int modCount = 0;
為了顯示美觀,對jdk原文注釋進行了換行操作。
原文大意如下:
該字段表示list結構上被修改的次數。結構上的修改指的是那些改變了list的長度大小或者使得遍歷過程中產生不正確的結果的其它方式。
該字段被Iterator以及ListIterator的實現類所使用,如果該值被意外更改,Iterator或者ListIterator 將拋出ConcurrentModificationException異常,
這是jdk在面對迭代遍歷的時候為了避免不確定性而采取的快速失敗原則。
子類對此字段的使用是可選的,如果子類希望支持快速失敗,只需要覆蓋該字段相關的所有方法即可。單線程調用不能添加刪除terator正在遍歷的對象,
否則將可能拋出ConcurrentModificationException異常,如果子類不希望支持快速失敗,該字段可以直接忽略。
----------------------------------------
例子代碼:
單線程操作,添加或者刪除元素。
public void deleteTest(){
List<String> list = new ArrayList();
list.add("aaaaaa");
list.add("bbbbbb");
list.add("cccccc");
list.add("dddddd");
list.add("eeeeee");
Iterator it = list.iterator();
int i = 0;
String s = null;
while(it.hasNext()){
if(i==2){
it.remove();// 如果用list.remove(it.next());會報異常
}
System.out.println("第"+i+"個元素"+it.next());
i++ ;
}
System.out.println("----------------");
Iterator it2 = list.iterator();
while(it2.hasNext()){
System.out.println(it2.next());
}
}
注意:第14行,如果用list.remove(it.next());會報ConcurrentModificationException異常,原因參上。
另:注意it.remove()刪除的是最近的一次it.next()獲取的元素,而不是當前iterator中游標指向的元素!!
因此,本例中i==2時,刪除的其實是bbbbbb,而不是cccccc,這很容易被忽視或者誤解。如果想刪掉cccccc,
正確操作是先調用it.next()獲取到具體元素,再判斷;而且由於調用了it.next(),此時游標已經指向我們期望刪除的值了。
想直接數數字進行刪除,在這里會容易出錯誤。
其實我們可以查看Iterator的源碼來驗證:
public Iterator<E> iterator() {
return new Itr();
}
返回的是一個Itr對象,這個Itr是ArrayList的內部類
private class Itr implements Iterator<E> {
int cursor; // index of next element to return //下一個元素的游標
int lastRet = -1; // index of last element returned; -1 if no such //上一個元素的
int expectedModCount = modCount; //修改計數器期望值
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor; //此時的游標,指向的是本次要遍歷的對象,因為上一次已經++了,初始值為0,沒有++的情況下是第一個元素
if (i >= size) //越界了
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1; //游標指向了下一個元素, 但 i 的值沒有變
return (E) elementData[lastRet = i]; //將 i 賦值給lastRet,取的值是方法開始時int i=cursor;中的cursor指向的值,而且最終這個游標的數值賦值給了lastRet
}
public void remove() {
if (lastRet < 0) // 如果沒有next()操作就直接remove的話,lastRet=-1,會拋異常
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); // remove之前,cursor、lastRet的值沒有修改,都是上次next之后的值,因此此處的lastRet指向上次next獲取的元素
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 手動將ArrayList.remove()后modCount的值賦給expectedModCount,避免引起不一致
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
以上代碼告訴我們,iterator.remove()實際是remove了上次next返回的元素,並且為了防止ConcurrentModificationException異常,手動修復了修改計數的期望值,而且如果沒有經過next操作就直接remove的話,會因為初始的lastRet=-1而拋出IllegalStateException異常。
