在 java 中,ArrayList 是一個很常用的類,在編程中經常要對 ArrayList 進行增、刪、改、查操作。之前在學校時一直認為刪除操作是最簡單的,現在才越發覺得自己愚蠢。只需要設置好預期條件的查詢才是最簡單的,刪除涉及到存儲空間的釋放,以及數組的遍歷等問題,在list的操作中相對還算小老哥呢。
這兩天在給小程序提供后台接口,因為設計的改變,需要對於已查詢出來的數組進行遍歷刪除。在使用 remove 方法對 ArrayList 進行刪除操作時,報 java.util.ConcurrentModificationException 異常,下面探討一下該異常的原因以及解決辦法。
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub List<String> listA = new ArrayList<>(); listA.add("a"); listA.add("b"); listA.add("c"); listA.add("d"); listA.add("e"); listA.add("f"); for(String str:listA){ if (str == "c") { listA.remove(str); } } } }
運行代碼發現,在調用 listA.remove(str) 時,會報 java.util.ConcurrentModificationException異常,如下圖:
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)
debug 單步發現,在刪除倒數第二個元素是,是不會報上面的錯誤的,除此之外都會報異常。既然發現了規律,那么可以從源碼去尋找原因。
我們可以發現 java for循環的實現,是將List 對象托管給迭代器 Iterator,即如果想對List 進行刪除操作,均需要經過 Iterator,否則在遍歷時會亂掉,故拋出 ConcurrentModificationException 異常。
那么,我們可以看一下 Iterator 迭代器迭代的步驟:
1、判斷是否有下個元素:iterator.hasNext()
public boolean hasNext() { return cursor != size; }
2、獲取下個元素並賦值給上面例子中的item變量:item = iterator.next()
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]; }
經過調試發現,checkForComodification時返回了異常,異常原因為 modCount != expectedModCount:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
繼續跟源碼,發現:
a.modCount 是 List 從被 new 新建之后被修改的次數,當 List 調用 remove()等方法時,modCount++;
b.expectedModCount 是 Itreator 期望當前的List被修改的次數;
c.在Iterator初始化的時候將modCount 的值賦給了expectedModCount;
那么,現在就很容易可以知道為什么會報異常了:
1).modCount 會隨着調用List.remove方法而自動增減,而expectedModCount則不會變化,就導致modCount != expectedModCount;
2).在刪除倒數第二個元素后,cursor=size-1,此時size=size-1,導致 hasNext 方法認為遍歷結束;
解決方案:
經過查閱源碼可以發現,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(); } }
修改后的代碼如下:
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub List<String> listA = new ArrayList<>(); listA.add("a"); listA.add("b"); listA.add("c"); listA.add("d"); listA.add("e"); listA.add("f");
Iterator<String> it_b=listA.iterator();
while(it_b.hasNext()){ String a=it_b.next(); if ("c".equals(a)) { it_b.remove(); } }
}
}
改完之后,啟動 run,運行順暢無比,借金星小姐姐(or 小哥哥??)的話作為結尾:完美~~
參考: