Java迭代器升級版探究

來源:互聯網
上載者:User

標籤:protect   一次迴圈   color   nts   aaa   ransient   do it   eth   foreach   

Alei最近和迭代器較上了勁,之前自以為深究過迭代器,不成想原來是坐井觀天,以蠡測海。上文中寫的東西哪裡算什麼深入探究?!但亡羊補牢,猶未遲也,經我多次實驗,終於弄懂其中某些精巧機制,閑話少說,我們進入正題。

注意,之後所有的知識點都以 ArrayList 這個容器類為例來進行詳細說明

在討論這個問題之前我們得首先在意兩個成員變數:

1、ArrayList 類裡繼承於 AbstractList 類的成員變數 modCount:

protected transient int modCount = 0;

2、ArrayList 類的私人內部類 Itr 裡的成員變數 expectedModCount:

int expectedModCount = modCount;

再看下Itr類源碼:

private class Itr implements Iterator<E> {    int cursor;       // index of next element to return    int lastRet = -1; // index of last element returned; -1 if no such    int expectedModCount = modCount;    public boolean hasNext() {        return cursor != size;    }    @SuppressWarnings("unchecked")    public E next() {        checkForComodification();        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);            cursor = lastRet;            lastRet = -1;            expectedModCount = modCount;        } catch (IndexOutOfBoundsException ex) {            throw new ConcurrentModificationException();        }    }    @Override    @SuppressWarnings("unchecked")    public void forEachRemaining(Consumer<? super E> consumer) {        Objects.requireNonNull(consumer);        final int size = ArrayList.this.size;        int i = cursor;        if (i >= size) {            return;        }        final Object[] elementData = ArrayList.this.elementData;        if (i >= elementData.length) {            throw new ConcurrentModificationException();        }        while (i != size && modCount == expectedModCount) {            consumer.accept((E) elementData[i++]);        }        // update once at end of iteration to reduce heap write traffic        cursor = i;        lastRet = i - 1;        checkForComodification();    }    final void checkForComodification() {        if (modCount != expectedModCount)        throw new ConcurrentModificationException();    }}

當我們使用 ArrayList 容器的 iterator() 方法後,在棧空間裡建立了一個此類特定的迭代器對象,同時將成員變數 modCount 的值賦予成員變數 expectedModCount。知道這個有趣的事情後可以先打住,讓我們再來看看 ArrayList 類 remove() 方法的源碼:

參數為 int 類型的 remove():

public E remove(int index) {    rangeCheck(index);    modCount++;    E oldValue = elementData(index);    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    return oldValue;}

參數為 Object 類型的 remove():

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;}/* * Private remove method that skips bounds checking and does not * return the value removed. */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}

以 ArrayList 類裡 remove() 方法為例來看,只要我們調用此方法一次,那麼 modCount 便自加一次 1。於是我們可以理解,modCount 是一個記錄容器物件修改次數的變數,它是一個計數器。小夥伴門完全可以去查源碼,不僅僅是 remove(),凡是涉及對 ArrayList 對象的增、刪、改的任何一種方法,當我們調用一次這類方法,那 modCount 便會自加一次 1,即,記錄一次容器物件的改變。例如,當我建立一個 ArrayList 對象 al 後,我調用 al.add() 一次,調用 al.remove() 一次,再調用 al.add() 一次後,那麼 modCount = 3,由此說明 al 被修改了3次。

在沒有建立迭代器對象之前的任何對容器物件的增刪改操作只會讓 modCount 自加,當我們建立一個對應容器類的迭代器對象之後,int expectedModCount = modCount,迭代器對象裡的 expectedModCount 成員變數被初始化為與 modCount 裡的數值一樣的值。

有了迭代器,然後用迭代器進行迭代,就涉及到迭代器對象的 hasNext();next()方法了,我們看下這兩個方法的源碼:

public boolean hasNext() {    return cursor != size;}@SuppressWarnings("unchecked")public E next() {    checkForComodification();    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];}

由此可見,兩個方法都不會改變 expectedModCount 的值,那怎麼理解 expectedModCount 這個成員變數呢?再看迭代器裡的 remove() 方法源碼:

public void remove() {    if (lastRet < 0)        throw new IllegalStateException();    checkForComodification();    try {        ArrayList.this.remove(lastRet);        cursor = lastRet;        lastRet = -1;        expectedModCount = modCount;    } catch (IndexOutOfBoundsException ex) {        throw new ConcurrentModificationException();    }}

在 remove() 方法的方法體裡,有“expectedModCount = modCount; “這樣一行語句,那麼不管我們調用多少次迭代器的 remove() 方法,始終會讓 expectedModCount 的數值等於 modCount 的值,這裡的 expectedModCount 可理解為使用迭代器對容器類對象進行修改的”期望修改次數“,就是說:迭代器裡的這個”期望修改次數“一定要和已經記錄下的容器的修改次數 modCount 一樣,那麼當你通過迭代器對容器類對象遍曆並進行修改時,使用迭代器本身的 remove() 才有意義(即讓 expectedModCount = modCount)!!而在 next() 方法體裡首行的 checkForComodification() 方法是這樣定義的:

final void checkForComodification() {    if (modCount != expectedModCount)        throw new ConcurrentModificationException();}

看了源碼,我們應該知道: checkForComodification() 的作用是檢查 expectedModCount 和 modCount 的值是否相等,如果不等,則拋出 ConcurrentModificationException 這個異常。這下顯而易見了,在我們通過迭代器進行遍曆時,若使用非迭代器對象提供的修改容器類對象的任何方法,則 modCount 的值增大,而 expectedModCount 地值不發生改變,那麼在進入下一次迴圈時,next() 方法體裡首行的 checkForComodification() 方法檢查到 expectedModCount 與 modCount 不等後拋出了 ConcurrentModificationException。

那麼,在通過迭代器進行迭代時,容器物件裡的任何元素都不能通過容器類所提供的方法進行增刪改的操作嗎?非也非也,Alei留下這樣一段代碼:

public static void main(String[] args) {    Collection c = new ArrayList();    c.add(new String("aaa"));    c.add(new String("bbb"));    c.add(new String("ccc"));    c.add(new String("ddd"));    c.add(new String("fff"));    c.add(new String("eee"));    for (Object o : c) {//      System.out.print(o + " ");        if (o.equals("fff")) {            c.remove(o);        }    }    System.out.println(c);}

當我們運行這段程式,你將會發現 ”fff“ 這個字串對象怎麼被成功刪除了?!這也是我之前一直疑惑且略顯白癡的地方。其實,我可以下定結論:在通過迭代器進行迭代時,容器物件裡的倒數第二個元素一定可以過容器類所提供的 remove() 方法進行刪除操作(不管這個容器的 size 有多大)。這又是為什麼呢?哈哈,對於這個問題,留待小夥伴們自行解決吧^_^!

 

Java迭代器升級版探究

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.