2015-08-26 45 views
5

這是爲什麼合法的:在計算循環除去列表元素VS迭代

for(int i=0; i < arr.size(); i++) { 
    arr.remove(i); 
} 

但使用迭代器或一個語法糖各產生一個ConcurrentModificationException

for(String myString : arr) { 
    arr.remove(myString); 
} 
  • 在每個人開始跳躍的時候,告訴我要使用iterator.remove();我在問爲什麼會有不同的行爲,而不是如何避免conc mod異常。謝謝。

回答

3

讓我們來看看如何,例如,ArrayLists的迭代器來實現:

private class Itr implements Iterator<E> { 
    int cursor;  // index of next element to return 
    int lastRet = -1; // index of last element returned; -1 if no such 

    public E next() { 
     checkForComodification(); 
     int i = cursor; 
     if (i >= size) throw new NoSuchElementException(); 
     // ... 
     cursor = i + 1; 
     return (E) elementData[lastRet = i]; 
    } 

    public void remove() { 
     // ... 
     ArrayList.this.remove(lastRet); 
     // ... 
     cursor = lastRet; 
     lastRet = -1; 
    } 

讓我們來看一個例子:

List list = new ArrayList(Arrays.asList(1, 2, 3, 4)); 
Iterator it = list.iterator(); 
Integer item = it.next(); 

我們刪除第一個元素

list.remove(0); 

如果我們現在想要調用it.remove(),迭代器將刪除number 2因爲這是lastRet指向的字段。

if (item == 1) { 
    it.remove(); // list contains 3, 4 
} 

這將是不正確的行爲!迭代器的合約指出remove()刪除next()返回的最後一個元素,但在併發修改情況下無法保留其合約。因此它選擇安全並拋出異常。

其他館藏的情況可能更加複雜。如果您修改HashMap,則可能會根據需要增加或減少。那時候,元素將落入不同的桶中,並且迭代器保持指向桶的指針,之後重新刷新將完全丟失。

請注意iterator.remove()本身並沒有拋出異常,因爲它能夠更新這兩個它自己的內部狀態和集合。然而,在相同實例集合的兩個迭代器上調用remove()會拋出,因爲它會使其中一個迭代器處於不一致狀態。

2

看着你的代碼,我假設arr是一個List。在你的名單上直接操作,而上面的循環,當你檢查

i < arr.size() 

所以,如果你刪除一個元素,我有比較較小值「重新校準」你的病情在頂部。 另一方面,在第二種情況下,您在迭代器實例化後對集合進行操作,並且不會真正重新校準自己。

希望這會有所幫助。

1

在第一個中,您正在修改一個數組,它並未被用作for循環的迭代器。

在第二個中,您試圖訪問一個數組,它正在被修改,同時您在循環中迭代它。這就是爲什麼它會拋出ConcurrentModificationException

+2

對於downvoter:請不要只是downvote答案,請解釋你看到什麼是錯的。我會非常感激。 –

+0

真的,對於下流者,請告訴我什麼是錯的,或者你認爲我應該改變什麼。如果我錯了,請糾正我。我很好奇。 –