在工做和學習中,常常碰到刪除ArrayList裏面的某個元素,看似一個很簡單的問題,卻很容易出bug。不妨把這個問題當作一道面試題目,我想必定能難道很多的人。今天就給你們說一下在ArrayList循環遍歷並刪除元素的問題。首先請看下面的例子:java
import java.util.ArrayList; public class ArrayListRemove { publicstaticvoidmain(String[]args) { ArrayList<String>list=newArrayList<String>(); list.add("a"); list.add("b"); list.add("b"); list.add("c"); list.add("c"); list.add("c"); remove(list); for(Strings:list) { System.out.println("element : "+s); } } public static void remove(ArrayList<String> list) { // TODO: } }
若是要想刪除list的b字符,有下面兩種常見的錯誤例子:面試
錯誤寫法實例一:數組
public static void remove(ArrayList<String> list) { for(inti=0;i<list.size();i++) { Strings=list.get(i); if(s.equals("b")) { list.remove(s); } } }
錯誤的緣由:這種最普通的循環寫法執行後會發現第二個「b」的字符串沒有刪掉。併發
錯誤寫法實例二:學習
public static void remove(ArrayList<String> list) { for(Strings:list) { if(s.equals("b")) { list.remove(s); } } }
錯誤的緣由:這種for-each寫法會報出著名的併發修改異常:java.util.ConcurrentModificationException。spa
先解釋一下實例一的錯誤緣由。翻開JDK的ArrayList源碼,先看下ArrayList中的remove方法(注意ArrayList中的remove有兩個同名方法,只是入參不一樣,這裏看的是入參爲Object的remove方法)是怎麼實現的:code
public boolean remove(Objecto){ if(o==null){ for(intindex=0;index<size;index++) if(elementData[index]==null){ fastRemove(index); return true; } }else{ for(intindex=0;index<size;index++) if(o.equals(elementData[index])){ fastRemove(index); return true; } } return false; }
通常狀況下程序的執行路徑會走到else路徑下最終調用faseRemove方法:blog
private void fastRemove(int index){ modCount++; intnumMoved=size-index-1; if(numMoved>0) System.arraycopy(elementData,index+1,elementData,index,numMoved); elementData[--size]=null;// Let gc do its work }
能夠看到會執行System.arraycopy方法,致使刪除元素時涉及到數組元素的移動。針對錯誤寫法一,在遍歷第一個字符串b時由於符合刪除條件,因此將該元素從數組中刪除,而且將後一個元素移動(也就是第二個字符串b)至當前位置,致使下一次循環遍歷時後一個字符串b並無遍歷到,因此沒法刪除。針對這種狀況能夠倒序刪除的方式來避免:element
public static void remove(ArrayList<String> list) { for(inti=list.size()-1;i>=0;i--) { Strings=list.get(i); if(s.equals("b")) { list.remove(s); } } }
由於數組倒序遍歷時即便發生元素刪除也不影響後序元素遍歷。rem
接着解釋一下實例二的錯誤緣由。錯誤二產生的緣由倒是foreach寫法是對實際的Iterable、hasNext、next方法的簡寫,問題一樣處在上文的fastRemove方法中,能夠看到第一行把modCount變量的值加一,但在ArrayList返回的迭代器(該代碼在其父類AbstractList中):
public Iterator<E> iterator() { return new Itr(); }
這裏返回的是AbstractList類內部的迭代器實現private class Itr implements Iterator,看這個類的next方法:
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
第一行checkForComodification方法:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
這裏會作迭代器內部修改次數檢查,由於上面的remove(Object)方法修改了modCount的值,因此纔會報出併發修改異常。要避免這種狀況的出現則在使用迭代器迭代時(顯示或for-each的隱式)不要使用ArrayList的remove,改成用Iterator的remove便可。
public static void remove(ArrayList<String> list) { Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("b")) { it.remove(); } } }