增強for為什麼不能刪除集合裡的元素

冷暖不知發表於2020-09-23

1.先看看ArrayList物件呼叫iterator()返回的Iterator物件
public Iterator iterator() {
return new Itr();
}
123
2.再看看ArrayList中實現Iterator的內部類Itr
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such

}
12345

cursor表示下一個返回元素的下標,可以理解成 遊標;lastRet表示上一次返回的元素下標。另ArrayList有個size屬性,表示ArrayList中的元素個數。

3.看迭代器的hasNext()方法
public boolean hasNext() {
return cursor != size;
}
123

hasNext() 的判斷條件是cursor != size. 只要沒遍歷到最後一個元素,就返回true.

4.下面是next()
public E next() {

int i = cursor; // cursor為當前需要返回元素的下標

cursor = i + 1; // cursor向後移動一個位置,指向下一個要返回的元素
return (E) elementData[lastRet = i]; // 對lastRet賦值,然後返回當前元素
}
1234567
5.現在看下例子
ArrayList list = new ArrayList();
list.add(“e1”);
list.add(“e2”);
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
if(“e1”.equals(str)) {
list.remove(“e1”);
}
}
12345678910
執行會報錯,這種操作是不被允許的。

6.解釋:
第一次 呼叫var2.hasNext(),此時滿足條件 cursor(0) != size(2),然後執行 var2.next(),此時 cursor=1
執行 list.remove(“e1”),此時,list的size將從2變為1當執行完第一次迴圈,進入第二次hasNext()判斷時,cursor=1而且size=1,導致Iterator認為已經遍歷結束,因此e2將被漏掉。此時,過程已非常清楚。list本有2個元素,Iterator第一次獲取元素時,程式刪掉了當前元素,導致list的size變為1。Iterator第二次獲取元素時,開心說到:”list一共只有一個元素,我已經遍歷了一個,easy,輕鬆搞定!”。
矛盾點在於:hasNext() 是根據已fetch元素和被遍歷物件的size動態判斷的,一旦遍歷過程中被遍歷物件的size變化,就會出現以上問題。
7.正確的姿勢,在使用迭代器遍歷的時候對集合元素進行增刪
將remove操作交給Iterator來處理,使用Iterator介面提供的remove操作。
List list = new ArrayList<>();
list.add(“e1”);
list.add(“e2”);

for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
String str = iterator.next();
if (“e1”.equals(str)) {
iterator.remove();
}

if ("e2".equals(str)) {
    System.out.println("element 2 fetched");
}

}
1234567891011121314
執行結果:element 2 fetched 被正常列印出來。 那Iterator的remove()又是怎麼做的?下面是ArrayList中迭代器的remove方法。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
    ArrayList.this.remove(lastRet); // 呼叫ArrayList的remove移除元素,且size減1
    cursor = lastRet; // 將遊標回退一位
    lastRet = -1; // 重置lastRet
    expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
}

}
1234567891011121314
因為Iterator.remove()在執行集合本身的remove後,同時對遊標進行了 “校準”。

相關文章