坑:
list循環體中,對循環的list進行修改會報ConcurrentModificationException錯誤
今天寫了這麼一段代碼然後報錯
public static void main(String[] args) {
List <String> list = new ArrayList <> ();
list.add (null);
list.add (null);
list.add ("1");
for (String string : list) {
if(StringUtils.isEmpty (string)){
list.remove (string);
}
}
System.out.println (list.size ());
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at
簡單介紹下原因,文章最後有詳細介紹
因為元素在使用的時候發生了并發的修改,導緻異常抛出。但是删除完畢馬上使用break跳出,則不會觸發報錯。
建議使用下面寫法
public static void main(String[] args) {
List <String> list = new ArrayList <> ();
list.add (null);
list.add (null);
list.add ("1");
Iterator <String> iterator = list.iterator ();
for (Iterator <String> it = iterator; it.hasNext (); ) {
String string = it.next ();
if(StringUtils.isEmpty (string)){
it.remove (); //注意這裡
}
}
System.out.println (list.size ());
}
//或者這種寫法(循環方式不同)
public static void main(String[] args) {
List <String> list = new ArrayList <> ();
list.add (null);
list.add (null);
list.add ("1");
Iterator <String> iterator = list.iterator ();
while (iterator.hasNext ()) {
String string = iterator.next ();
if (StringUtils.isEmpty (string)) {
iterator.remove ();
}
}
System.out.println (list.size ());
}
從異常資訊可以發現,異常出現在checkForComodification()方法中。
我們不忙看checkForComodification()方法的具體實作,我們先根據程式的代碼一步一步看ArrayList源碼的實作:
首先看ArrayList的iterator()方法的具體實作,檢視源碼發現在ArrayList的源碼中并沒有iterator()這個方法,那麼很顯然這個方法應該是其父類或者實作的接口中的方法,我們在其父類AbstractList中找到了iterator()方法的具體實作,下面是其實作代碼:
public Iterator<E> iterator() {
return new Itr();
}
從這段代碼可以看出傳回的是一個指向Itr類型對象的引用,我們接着看Itr的具體實作,在AbstractList類中找到了Itr類的具體實作,它是AbstractList的一個成員内部類,下面這段代碼是Itr類的所有實作:
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
首先我們看一下它的幾個成員變量:
cursor:表示下一個要通路的元素的索引,從next()方法的具體實作就可看出
lastRet:表示上一個通路的元素的索引
expectedModCount:表示對ArrayList修改次數的期望值,它的初始值為modCount。
modCount是AbstractList類中的一個成員變量
protected transient int modCount = 0;
好了,到這裡我們再看看上面的程式: 該值表示對List的修改次數,檢視ArrayList的add()和remove()方法就可以發現,每次調用add()方法或者remove()方法就會對modCount進行加1操作。
當調用list.iterator()傳回一個Iterator之後,通過Iterator的hashNext()方法判斷是否還有元素未被通路,我們看一下hasNext()方法,hashNext()方法的實作很簡單:
public boolean hasNext() {
return cursor != size();
}
然後通過Iterator的next()方法擷取到下标為0的元素,我們看一下next()方法的具體實作: 如果下一個通路的元素下标不等于ArrayList的大小,就表示有元素需要通路,這個很容易了解,如果下一個通路元素的下标等于ArrayList的大小,則肯定到達末尾了。
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
接着往下看,程式中判斷目前元素的值是否為2,若為2,則調用list.remove()方法來删除該元素。 這裡是非常關鍵的地方:首先在next()方法中會調用checkForComodification()方法,然後根據cursor的值擷取到元素,接着将cursor的值賦給lastRet,并對cursor的值進行加1操作。初始時,cursor為0,lastRet為-1,那麼調用一次之後,cursor的值為1,lastRet的值為0。注意此時,modCount為0,expectedModCount也為0。
我們看一下在ArrayList中的remove()方法做了什麼:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
那麼注意此時各個變量的值:對于iterator,其expectedModCount為0,cursor的值為1,lastRet的值為0。 通過remove方法删除元素最終是調用的fastRemove()方法,在fastRemove()方法中,首先對modCount進行加1操作(因為對集合修改了一次),然後接下來就是删除元素的操作,最後将size進行減1操作,并将引用置為null以友善垃圾收集器進行回收工作。
對于list,其modCount為1,size為0。
接着看程式代碼,執行完删除操作後,繼續while循環,調用hasNext方法()判斷,由于此時cursor為1,而size為0,那麼傳回true,是以繼續執行while循環,然後繼續調用iterator的next()方法:
注意,此時要注意next()方法中的第一句:checkForComodification()。
在checkForComodification方法中進行的操作是:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
很顯然,此時modCount為1,而expectedModCount為0,是以程式就抛出了ConcurrentModificationException異常。 如果modCount不等于expectedModCount,則抛出ConcurrentModificationException異常。
到這裡,想必大家應該明白為何上述代碼會抛出ConcurrentModificationException異常了。
關鍵點就在于:調用list.remove()方法導緻modCount和expectedModCount的值不一緻。