【搬砖系列】如何在遍历List时安全删除集合元素
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; 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 }
在方法中首先将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(); }
同理,其他集合删除应该也是这个步骤,没来得及验证,欢迎评论区小锤换大锤:)。