【搬磚系列】如何在遍歷List時安全刪除集合元素
阿新 • • 發佈:2018-11-02
1 public void testIterRemove() { 2 List l1 = new ArrayList(Arrays.asList(1, 2, 3, 4, 5)); 3 Iterator<Integer> iterator = l1.iterator(); 4 System.out.println("before=" + l1); 5 while (iterator.hasNext()) { 6 iterator.next(); 7遍歷時移除元素的正確示例iterator.remove(); 8 } 9 System.out.println("after=" + l1); 10 } 11 /* 12 before=[1, 2, 3, 4, 5] 13 after=[] 14 */
程式碼中關鍵的部分在於:
iterator.next();
和
iterator.remove();
二者缺一不可。當然呼叫next()之前要呼叫hasNext()確保沒有越界。
簡單分析兩個方法的程式碼即可得出答案:
首先看remove()方法,為什麼hasNext() + 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; 9 lastRet = -1;ArrayList#Itr.remove()10 expectedModCount = modCount; 11 } catch (IndexOutOfBoundsException ex) { 12 throw new ConcurrentModificationException(); 13 } 14 }
由於程式碼用屬性lastRet(即lastReturn index)控制異常,所以檢視ArrayList#Itr的例項初始化語句,lastRet初值是-1,如果單獨呼叫remove()會報錯。
再看一下next()方法,就會找出答案:
1 public E next() { 2 checkForComodification(); 3 int i = cursor; 4 if (i >= size) 5 throw new NoSuchElementException(); 6 Object[] elementData = ArrayList.this.elementData; 7 if (i >= elementData.length) 8 throw new ConcurrentModificationException(); 9 cursor = i + 1; 10 return (E) elementData[lastRet = i]; 11 }ArrayList#Itr.next()
在方法中首先將cursor記錄到變數 i,返回前將 i 的知賦給 lastRet ,完成lastRet的更新。方法返回值不是本次要考慮的範疇。
再回過頭看remove()方法,有了lastRet後,就可以重置cursor。在方法中呼叫了ArrayList#remove(),這個方法會重新調整陣列元素的位置,如果刪掉的不是最後一個位置的元素(numMoved == 0 表示刪掉的正好是最後一位元素),將會把index後面的元素往前移動一個,最後將elementData[--size] = null 為移除舊元素,幫助GC。
總結:
需要先呼叫next()更新lastRet的值,才能在remove()中通過第一關對lastRet的檢查。因此在遍歷List時需要聯合使用 :
while( it.hasNext() ) { it.next(); it.remove(); }