ArrayList在foreach正常迭代刪除不報錯的緣由

1、背景 html

在之前的隨筆中說道過ArrayList的foreach迭代刪除的問題:ArrayList迭代過程刪除問題java

按照之前的說法,在ArrayList中經過foreach迭代刪除會拋異常:java.util.ConcurrentModificationException數組

可是下面這段代碼實際狀況卻沒報異常,是什麼狀況?post

1     List<String> list = new ArrayList<String>();
2         list.add("1");
3         list.add("2");
4         for (String item : list) {
5             System.out.println("item:" + item);
6             if ("1".equals(item)) {
7                 list.remove(item);
8             }
9         }

2、分析 this

咱們知道ArrayList的foreach迭代調用的是ArrayList內部類Itr,Itr源碼以下:url

 

 1     private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount;
 5 
 6         public boolean hasNext() {
 7             return cursor != size;
 8         }
 9 
10         @SuppressWarnings("unchecked")
11         public E next() {
12             checkForComodification();
13             int i = cursor;
14             if (i >= size)
15                 throw new NoSuchElementException();
16             Object[] elementData = ArrayList.this.elementData;
17             if (i >= elementData.length)
18                 throw new ConcurrentModificationException();
19             cursor = i + 1;
20             return (E) elementData[lastRet = i];
21         }
22 
23         public void remove() {
24             if (lastRet < 0)
25                 throw new IllegalStateException();
26             checkForComodification();
27 
28             try {
29                 ArrayList.this.remove(lastRet);
30                 cursor = lastRet;
31                 lastRet = -1;
32                 expectedModCount = modCount;
33             } catch (IndexOutOfBoundsException ex) {
34                 throw new ConcurrentModificationException();
35             }
36         }
37 
38         final void checkForComodification() {
39             if (modCount != expectedModCount)
40                 throw new ConcurrentModificationException();
41         }
42     }

按照調用順序查看源碼spa

1.hasNext指針

ArrayList的foreach迭代首先調用的是ArrayList內部類ItrhasNext(),該方法會對當前循環指針和長度作判斷。只有當hasNext()返回true纔會執行foreach裏面的代碼,cursor = size時候就退出循環(由於這二者相等意味着都遍歷完了,假如ArrayList的size=2,那麼hasNext會被調用3次,可是第3次調用不會執行foreach裏面代碼)。code

2.若是上一步返回true的話會執行Itr的next(),若是數據無異常的話 cursor = i + 1;因此沒執行一次next()時cursor都會+1htm

3.接着會執行到 list.remove(item),此處調用的是ArrayList的remove(Object o)而不是Itr的,看下ArrayList的remove()的源碼:

 1 public boolean remove(Object o) {
 2         if (o == null) {
 3             for (int index = 0; index < size; index++)
 4                 if (elementData[index] == null) {
 5                     fastRemove(index);
 6                     return true;
 7                 }
 8         } else {
 9             for (int index = 0; index < size; index++)
10                 if (o.equals(elementData[index])) {
11                     fastRemove(index);
12                     return true;
13                 }
14         }
15         return false;
16     }

o != null,會進入else的fastRemove(index);能夠看到ArrayList根據傳入的值刪除會進行遍歷equals判斷,找到索引再經過fastRemove(index)刪除,所以List頻繁作刪除修改效率比較低。

4.再看下fastRemove()源碼:

1    */
2     private void fastRemove(int index) {
3         modCount++;
4         int numMoved = size - index - 1;
5         if (numMoved > 0)
6             System.arraycopy(elementData, index+1, elementData, index,
7                              numMoved);
8         elementData[--size] = null; // clear to let GC do its work
9     }

第8行會把該索引對應的數組的值置爲null,而且size-1。可是卻沒有進行 cursor - 1操做

至此明白了。此處的ArrayList經過foreach迭代刪除爲何不會報錯:

剛開始ArrayList的size=2時,cursor =0

①第一次hasNext()進來,cursor != size進入next()後cursor=1,接着由於知足條件刪除的時候size-1=1;

②第二次hasNext()進來,cursor = size = 1,因此不會執行foreach的代碼,也不會出現後面檢測modCount值拋ConcurrentModificationException

上述未拋異常的狀況主要是hasNext()中判斷遍歷完成的條件與ArrayList刪除後的數據恰好吻合而已。

因此只要知足條件:刪除的元素在循環時的指針cursor+1=size就會出現這種狀況!刪除ArrayList倒數第二個(即第 size - 1個元素)就會出現不拋異常的假象。

(例如size=3,刪除第2個元素;size=4,刪除第3個元素)

由於刪除後size-1=cursor

 public boolean hasNext() {

return cursor != size;

}

hasNext()會返回false,不會進入ArrayList的迭代器,就不會進入 next() 執行checkForComodification()

這是一種條件判斷下的特殊狀況,其餘狀況都會拋出異常,因此不要在foreach進行刪除數據。請在迭代器中進行刪除。

相關文章
相關標籤/搜索