1. 程式人生 > >foreach迴圈中為什麼不要進行remove/add操作

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時候,再次報錯。

相關推薦

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

不要foreach迴圈進行元素的remove/add操作

阿里巴巴java開發手冊的建議 在看阿里巴巴java開發手冊時,有一條建議是這樣的。 【強制】不要在foreach迴圈裡進行元素的remove/add操作。remove元素請使用Iterator方式,如果併發操作,需要對Iterator物件加鎖。 foreach遍歷集合,其實是走的Itera

為什麼在foreach迴圈進行元素remove/add操作,會拋ConcurrentModificationException 異常?

執行以下程式碼: @Test public void test() { List<String> list = new ArrayList<>(); list.add("A"); list.add("B");

不要foreach 循環裏進行元素的 remove / add 操作

foreach remove在foreach 循環裏面,不能用remove方法,因為當list裏面的最後一個元素被remove時候,會報錯。摘自《阿裏巴巴Java開發手冊》【強制】不要在 foreach 循環裏進行元素的 remove / add 操作。 remove 元素請使用 Iterator方式,如果並

為什麼阿里禁止在 foreach 迴圈進行元素的 remove/add 操作

在阿里巴巴Java開發手冊中,有這樣一條規定: 但是手冊中並沒有給出具體原因,本文就來深入分析一下該規定背後的思考。 1.

為什麽禁止在 foreach 循環裏進行元素的 remove/add 操作

詳細 控制 string 得到 each lec 就是 編譯 分享   首先看下邊一個例子,展示了正確的做法和錯誤的錯發:   這是為什麽呢,具體原因下面進行詳細說明: 1、foreach循環(Foreach loop)是計算機編程語言中的一種控制流程語句,通

為什麽阿裏禁止在 foreach 循環裏進行元素的 remove/add 操作

調用鏈 不想 增強 arr 9.png 為我 相等 手冊 正確姿勢 在阿裏巴巴Java開發手冊中,有這樣一條規定:但是手冊中並沒有給出具體原因,本文就來深入分析一下該規定背後的思考。1.foreach循環foreach循環(Foreach loop)是計算機編程語言中的一種

for循環裏面不要進行remove操作,for循環裏remove元素後,list的下標會減小,導致遍歷不完全

body 代碼 2.0 class equals tostring arraylist 執行 align p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 12.0px "Helvetica

foreach迴圈對list結構進行新增或刪除

在迴圈中對list進行新增或刪除操作,會丟擲currentModifierException,因為在迴圈過程中動態的加入或刪除list元素會導致list的元素數量改變,出現漏項或者無限迴圈等現象。 ·如果要在foreach迴圈中刪除list中的元素,要使用itrator迭代

Random在for以及foreach迴圈產生相同隨機數問題

在Random生成隨機數的時候,他是根據當前毫秒值來進行隨機生成,如果短時間內連續生成隨機數,就會導致生成的隨機數相同。下面我們介紹如何解決在 短時間內生成隨機數的時候,如何避免隨機數不一樣的問題。 利用Lock鎖住random引數 Random rand = new Random(); pub

16.1 foreach 迴圈捕獲變數的變化

  在 foreach 迴圈內的匿名函式(通常為Lambda表示式)中捕獲迴圈 變數時要格外小心。程式碼清單16-1就展示了這樣一個簡單的示例,它看上去似乎會輸出 x 、 y 、 z 。 1 string[] values = new string[] { "x", "y",

jquery實現json資料填充到table表格並且實現remove add 資料

window.onload=function(){ //var articles = getAllDbdata(); getAllDbdata(); $("#removebtn").bind("click", delOneRow); $("#addbtn").bind("click",

為什麼阿里巴巴Java開發手冊強制要求不要foreach迴圈進行元素的removeadd操作

在閱讀《阿里巴巴Java開發手冊》時,發現有一條關於在 foreach 迴圈裡進行元素的 remove/add 操作的規約,具體內容如下: 錯誤演示 我們首先在 IDEA 中編寫一個在 foreach 迴圈裡進行 remove 操作的程式碼: import java.util.ArrayList; imp

Java基礎之你會在foreach遍歷集合時進行remove操作嗎?

當通過for迴圈遍歷集合時,一般禁止操作(add or remove)集合元素。雖然開發規範裡寫的非常清楚,但最近還是有人掉坑裡導致出了一個小BUG,那我們就一起看看這麼做到底會發生什麼? 小例子 程式碼示例 List<String>

C# 對Foreach 的保護,因此在迭代的時候沒辦法remove add 等修改操作

InvalidOperationException: out of sync System.Collections.Generic.Dictionary`2+Enumerator[System.Int32,UnityEngine.GameObject].VerifyState

for迴圈進行Promise非同步操作的問題總結

筆者在for迴圈中進行Promise非同步操作的時候,主要會遇到兩個問題: 一是如何讓所有的for迴圈中的Promise操作結束後執行某個操作 二是迴圈中如果後一個Promise的執行依賴與前一個Promise的執行結果(例如對於某個資料庫操作

對比倆個Excel的數據,並且進行數據操作

color 通過 類庫 分享 並且 多條 ado.net .cn server 項目需要, 去對比倆個Excel中的數據是否一樣, 不一樣需要做替換, 有個同事在處理中(處理了一天 1000 多條吧,移交給我), 本人在想通過人工手動的方式,一個個做對比得花多長時間啊, 主

ZBrush如何對模型進行減面操作

del 分享 nbsp ati 凍結 cache 沒有 技術分享 技術 Decimation Master是ZBrush 4R8自帶的一個插件。中文名叫減面大師。其功能非常強大,也非常的方便,可以幫助我們提高效率,減少電腦資源損耗。作為一名3D美術師是必須掌握的一個技術。

對 url 含有的中文進行轉碼操作

www 勘誤 utili reason main letter ins 兩個 int 對 url 中含有的中文進行轉碼操作 一般情況下,將帶有中文的 url 拷貝到開發工具,開發工具都會有相應的轉碼(自動轉碼), 現在大部分的瀏覽器也可以對含有中文的 url 進行轉碼(自

使用js方法將table表格指定列指定行相同內容的單元格進行合並操作

var get 是否 for true ntb doc ide 就是 一、簡介 使用js方法對html中的table表格進行單元格的行列合並操作。 網上執行此操作的實例方法有很多,但根據實際業務的區別,大多不適用。 所以在網上各位大神寫的方法的基礎上進行了部分修改以適合自己