1. 程式人生 > >Iterator原始碼解析及和for-each迴圈

Iterator原始碼解析及和for-each迴圈

在使用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 策略,用來防止多執行緒併發修改集合時,保證資料的一致性。