在java語言中,ArrayList是一個很常用的類,在編程中經常要對ArrayList進行刪除操作,在使用remove方法對ArrayList進行刪除操作時,報java.util.ConcurrentModificationException異常,下面探討一下該異常的原因以及解決辦法。
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Test { 5 6 public static void main(String[] args) { 7 // TODO Auto-generated method stub 8 List<Integer> listA=new ArrayList<>(); 9 listA.add(1); 10 listA.add(2); 11 listA.add(3); 12 listA.add(4); 13 listA.add(5); 14 listA.add(6); 15 16 for(Integer a:listA){ 17 if (a==3) { 18 listA.remove(3); 19 } 20 } 21 } 22 }
上述代碼在刪除value=3的元素時,報java.util.ConcurrentModificationException異常,如下圖
1 Exception in thread "main" java.util.ConcurrentModificationException 2 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) 3 at java.util.ArrayList$Itr.next(ArrayList.java:851) 4 at com.zhang.Test.main(Test.java:19)
那么是不是刪除每一個元素都會報上面的異常呢?經過測試發現,刪除value=5的元素(倒數第二個元素)時,就不會報上面異常,除此之外均會報如上異常。(此處不再一一測試,有興趣可自己編碼測試)
我們通過上述測試發現了規律,既除了刪除倒數第二個錯誤不會異常,刪除其他元素均會異常。既然掌握了規律,那么就要從源碼層面揭露該異常的原因。首先發現Java的for循環,就是將List對象遍歷托管給Iterator,你如果要對list進行增刪操作,都必須經過Iterator,否則Iterator遍歷時會亂,所以直接對list進行刪除時,Iterator會拋出ConcurrentModificationException異常。
其實,每次foreach迭代的時候都有兩部操作:
第一步:iterator.hasNext() //判斷是否有下個元素
public boolean hasNext() { return cursor != size; }
第二步:item = iterator.next() //下個元素是什么,並賦值給上面例子中的item變量
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]; }
通過debug調試,我們發現,checkForComodification時返回了異常,異常原因為 modCount != expectedModCount。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
進一步閱讀源碼,發現:
1.modCount 時List從new開始,被修改的次數。當List調用Remove等方法時,modCount++
2.expectedModCount是指Iterator現在期望這個list被修改的次數是多少次。是在Iterator初始化的時候將modCount 的值賦給了expectedModCount
那么就解釋了為什么會報上述異常:
1.modCount 會隨着調用List.remove方法而自動增減,而expectedModCount則不會變化,就導致modCount != expectedModCount。
2.在刪除倒數第二個元素后,cursor=size-1,此時size=size-1,導致hasNext方法認為遍歷結束。
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub List<Integer> listA=new ArrayList<>(); listA.add(1); listA.add(2); listA.add(3); listA.add(4); listA.add(5); listA.add(6); for(Integer a:listA){ System.out.println(a); if (a==5) { listA.remove(5); } } } }
上述代碼加了打印后,輸出1,2,3,4,5;進一步證明最后一個元素6並沒有被遍歷到。
解決方法:
在找到原因后,則進一步進行解決
經過查閱源碼可以發現,iterator也有一個remove方法如下,其中有一個重要的操作為expectedModCount = modCount;這樣就保證了兩者的相等。
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(); } }
修改后的代碼如下:
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub List<Integer> listA=new ArrayList<>(); listA.add(1); listA.add(2); listA.add(3); listA.add(4); listA.add(5); listA.add(6); Iterator<Integer> it_b=listA.iterator(); while(it_b.hasNext()){ Integer a=it_b.next(); if (a==4) { it_b.remove(); } } for(Integer b:listA){ System.out.println(b); } } }
java.util.ConcurrentModificationException異常完美解決。