foreach循環中為什么不要進行remove/add操作


先來看一段代碼,摘自阿里巴巴的java開發手冊

1 List<String> a = new ArrayList<String>();
2  a.add("1");
3  a.add("2");
4  for (String temp : a) {
5      if("1".equals(temp)){
6          a.remove(temp);
7 } 
8 }

此時執行代碼,沒有問題,但是需要注意,循環此時只執行了一次。具體過程后面去分析。再來看一段會出問題的代碼:

List<String> a = new ArrayList<String>();
 a.add("1");
 a.add("2");
 for (String temp : a) {
     if("2".equals(temp)){
         a.remove(temp);
} 
}

輸出為:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at luyudepackage.waitTest.main(waitTest.java:57)

是不是很奇怪?接下來將class文件,反編譯下,結果如下

 1 List a = new ArrayList();
 2 a.add("1");
 3 a.add("2");
 4 Iterator i$ = a.iterator();
 5 do
 6 {
 7     if(!i$.hasNext())
 8         break;
 9     String temp = (String)i$.next();
10     if("1".equals(temp))
11         a.remove(temp);
12 } while(true);

幾個需要注意的點:

1.foreach遍歷集合,實際上內部使用的是iterator。

2.代碼先判斷是否hasNext,然后再去調用next,這兩個函數是引起問題的關鍵。

3.這里的remove還是list的remove方法。

先去觀察下list.remove()方法中的核心方法fastRemove()方法。

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

注意第二行,modCount++,此處先不表,下文再說這個參數。

順路觀察下list.add()方法

1 public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5     }

注意第二行的注釋,說明這個方法也會使modCount++

再去觀察下,iterator()方法

1 public Iterator<E> iterator() {
2         return new Itr();
3  }
 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     }

幾個需要注意的點:

1.在iterator初始化的時候(也就是for循環開始處),expectedModCount = modCount,猜測是和當時list內部的元素數量有關系(已證實)。

2.當cursor != size的時候,hasNext返回true

3.next()函數的第一行,checkForComodification()這個函數就是報錯的原因 這個函數就是萬惡之源

4.第39行,mod != expectedModCount 就會拋出ConcurrentModificationException()


 接下來分析文章開頭的第一個例子,為啥不會報錯?

第一個例子執行完第一次循環后,mod = 3 expectedModCount =2 cursor = 1 size = 1  所以程序在執行hasNext()的時候會返回false,所以程序不會報錯。

第二個例子執行完第二次循環后,mod = 3 expectdModCount = 2 cursor = 2 size = 1 此時cursor != size 程序認定還有元素,繼續執行循環,調用next方法但是此時mod != expectedModCount 所以此時會報錯。

道理我們都懂了,再看一個例子

 1 public static void main(String[] args) throws Exception {
 2         List<String> a = new ArrayList<String>();
 3         a.add("1");
 4         a.add("2");
 5         for (String temp : a) {
 6             System.out.println(temp);
 7             if("2".equals(temp)){
 8                 a.add("3");
 9                 a.remove("2");
10             } 
11         }
12 }

此時輸出為:

1

2

顯然,程序並沒有執行第三次循環,第二次循環結束,cursor再一次等於size,程序退出循環。

與remove類似,將文章開頭的代碼中remove替換為add,我們會發現無論是第一個例子還是第二個例子,都會拋出ConcurrentModificationException錯誤。

原因同上,代碼略。


手冊上推薦的代碼如下

1 Iterator<String> it = a.iterator(); while(it.hasNext()){
2 String temp = it.next(); if(刪除元素的條件){
3         it.remove();
4        }
5 }

此時remove是iterator的remove,我們看一下它的源碼:

 1  public void remove() {
 2             if (lastRet < 0)
 3                 throw new IllegalStateException();
 4             checkForComodification();
 5 
 6             try {
 7                 ArrayList.this.remove(lastRet);
 8                 cursor = lastRet;   //index of last element returned;-1 if no such
 9                 lastRet = -1;
10                 expectedModCount = modCount;
11             } catch (IndexOutOfBoundsException ex) {
12                 throw new ConcurrentModificationException();
13             }
14         }

注意第10行,第8行,所以此時程序不會有之前的問題。

但是手冊上推薦的方法,在多線程環境還是有可能出現問題,一個線程執行上面的代碼,一個線程遍歷迭代器中的元素,同樣會拋出CocurrentModificationException。

如果要並發操作,需要對iterator對象加鎖。


平時遍歷list,然后刪除某個元素的時候,如果僅僅刪除第一個且刪除之后調用break  //代表着此時不會再去執行iterator.next方法 也就不會觸發萬惡之源

而如果要刪除所有的某元素,則會報錯,謹記!

 Ps再來看一個佐證

public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            for(int i : list){
                System.out.println(i);
                if(i == 2){
                    list.remove((Object)2);
                }
            }

        }

此時只會輸出

1

2

當把remove對象改為3時候,再次報錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM