Iterator原始碼解析及和for-each迴圈
阿新 • • 發佈:2018-12-09
在使用foreach迴圈遍歷集合的過程中,如果集合被修改了,會丟擲ConcurrentModificationException異常。
以這段程式碼為例子:
public class SynProblem { List<Widget> widgetList = Collections.synchronizedList(new ArrayList<>()); { widgetList.add(new Widget(1)); widgetList.add(new Widget(2)); widgetList.add(new Widget(3)); } public void test() { for (Widget w : widgetList) { doSomethings(w); } } private void doSomethings(Widget w) { System.out.println(w); } public static void main(String[] args) { SynProblem s = new SynProblem(); s.test(); } }
使用javap反編譯一下,可以看到在test方法中,for迴圈呼叫了iterator介面的方法
問題來了,foreach迴圈底層也是呼叫了iterator的方法,為什麼在foreach迴圈add/remove集合元素,可能會丟擲 ConcurrentModificationException 異常,但是使用iterator來remove 集合元素卻不會呢?
下面以ArrayList為例:
呼叫ArrayList.iterator()返回的是ListItr,這是ArrayList的內部類,實現了Iterator介面
private class Itr implements Iterator<E> { int cursor; //下一個返回元素索引 int lastRet = -1; //最後一個返回元素索引,如果被remove該索引會被重置為-1 int expectedModCount = modCount;//期待修改次數(可被視為 Itr內部記錄的集合結構變化次數) Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification();//1. int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet);//呼叫集合的刪除操作,modCount+1 cursor = lastRet; lastRet = -1; expectedModCount = modCount;//重新給expectedModCount賦值 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount)//如果期待修改次數與實際修改次數不同,則丟擲異常 throw new ConcurrentModificationException(); } }
而ArrayList的add方法,也會進行modCount ++
可以看到的是在foreach迴圈會呼叫next()方法,next方法會呼叫checkForComodification()來檢查modCount 是否等於expectedModCount。要注意的是如果在for迴圈中直接使用add或者remove操作,是不會有下面這一步的。也就是說此時modCount != expectedModCount。丟擲異常。這是java的 fail-fast 策略,用來防止多執行緒併發修改集合時,保證資料的一致性。