如下代碼,想要循環刪除列表中的元素b,該怎么處理?
public class ListDemo { public static void main(String[] args) { ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add("a"); arrayList.add("b"); arrayList.add("b"); arrayList.add("c"); arrayList.add("d"); arrayList.add("e"); remove(arrayList); // remove2(arrayList); // remove3(arrayList); System.out.println(arrayList); } }
方法一:for循環遍歷
public static void remove(ArrayList<String> list) { for (int i = 0; i < list.size(); i++) { String s = list.get(i); if (s.equals("b")) { list.remove(s); } } }
輸出結果:
[a, b, c, d, e]
由結果可知,第二個元素b並未刪除,原因是當第一個元素b被刪除后,它后面所有的元素都向前移動了一個單位,循環時導致第二個元素b漏掉了(本例中從下標2變為了下標1,而下標1已經遍歷過了),可以通過源碼來看:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
進入 fastRemove方法:
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0)
// 被刪除元素后面所有的元素都向前移動 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
方法二:foreach循環
public static void remove2(ArrayList<String> list) { for (String s : list) { if (s.equals("b")) { list.remove(s); } System.out.println(s); } }
會報錯:java.util.ConcurrentModificationException。這是因為在這里,foreach循環遍歷容器本質上是使用迭代器進行遍歷的,會對修改次數modCount進行檢查,不允許集合進行更改操作,源碼如下:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
方法三:使用迭代器遍歷刪除
public static void remove3(ArrayList<String> list) { Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("b")) { it.remove(); } } }
使用迭代器遍歷刪除時,能夠避免方法二中出現的問題。這是因為:在ArrayList中,modCount是指集合的修改次數,當進行add或者delete時,modCount會+1;expectedModCount是指集合的迭代器的版本號,初始值是modCount,但是當集合進行add或者delete操作時,modCount會+1,而expectedModCount不會改變,所以方法二中會拋出異常。但是it.remove操作時,會同步expectedModCount的值,把modCount的值賦予expectedModCount。所以不會拋出異常。it.remove的源碼如下:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); // 移除元素 cursor = lastRet; lastRet = -1; expectedModCount = modCount; // 同步expectedModCount的值 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
總結:如果想正確的循環遍歷刪除元素,需要使用方法三,也就是迭代器遍歷刪除的方法。