Java集合迭代器

迭代器模式定義

就是提供一種方法對一個容器對象中的各個元素進行訪問,而又不暴露該對象容器的內部細節。這意味着迭代器需要提供統一的接口。

普通訪問

我們先來看下正常訪問集合

訪問數組

int array[] = new int[3];    
 for (int i = 0; i < array.length; i++) {
     System.out.println(array[i]);
}

訪問List

List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ;  i++) {
    String string = list.get(i);
}

我們可以看出,以上兩種方式,我們總是知道集合的內部結構。訪問集合元素的代碼是和集合本身緊密耦合的。無法將訪問遍歷邏輯從集合類客戶端代碼抽離出來。不同的集合會有不用的遍歷代碼。所以纔有Iterator,它總是用同一種邏輯來遍歷集合。使得客戶端自身不需要來維護集合的內部結構,所有的內部狀態都由Iterator來維護。客戶端不用直接和集合進行打交道,而是控制Iterator向它發送向前向後的指令,就可以遍歷集合。

Java迭代器

  1. java.util.Iterator

先看下迭代器接口的定義

package java.util;
public interface Iterator<E> {
    boolean hasNext();//判斷是否存在下一個對象元素

    E next();//獲取下一個元素

    void remove();//移除元素
}

2.Iterable

Java中還提供了一個Iterable接口,Iterable接口實現後的功能是‘返回’一個迭代器,我們常用的實現了該接口的子接口有:Collection、List、Set等。該接口的iterator()方法返回一個標準的Iterator實現。實現Iterable接口允許對象成爲Foreach語句的目標。就可以通過foreach語句來遍歷你的底層序列。

Iterable接口包含一個能產生Iterator對象的方法,並且Iterable被foreach用來在序列中移動。因此如果創建了實現Iterable接口的類,都可以將它用於foreach中。

Package java.lang;

import java.util.Iterator;
public interface Iterable<T> {
    Iterator<T> iterator();
}

Java中幾乎所有的集合都直接或間接提供了遍歷本集合的迭代器實現。其作爲內部類存在於集合類內部。下面我們看一個最簡單的迭代器實現——ArrayList的迭代器。

ArrayList迭代器

在ArrayList內部類Itr實現了Iterator接口,提供next() hasNext() remove()等方法。先看下維護的幾個變量

 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
        //預期被修改的次數值,初始值等於modCount
        //modCount是ArrayList維護的變量,當進行list.add()/remove()操作時會修改這個值
        int expectedModCount = modCount;
        .....
    }    
  • next()
        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];
        }

首先進行檢查,checkForComodification()是Itr內部函數,如下

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

檢查期待修改次數和真正修改次數modCount是否相等,不相等拋出異常
接下來獲取下一個元素位置下標cursor進行判斷,邏輯很簡單不解釋。最終取出元素數據數組中相應值返回並將cursor遞增。同時將lastRet遞增

  • 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();
            }
        }

首先判斷lastRet是否小於零,小於零則拋出異常。由於初始值爲-1並且只有在next()方法中才操作lastRet變量,所以迭代時不next()而直接remove()會報錯,這點也很好理解。然後是進行checkForComodification()。接着調用集合remove()方法。並將expectedModCount重新賦值,這點很重要。這也是爲什麼通過迭代器遍歷List時,使用迭代器remove()方法刪除元素沒有問題,通過list.remove()刪除就會報錯。後邊會詳細解釋。

  • 外部調用
        List<String> list=new ArrayList<>();
        list.add("abc");
        list.add("edf");
        list.add("ghi");
        for(Iterator<String> it=list.iterator();it.hasNext();)
        {
            System.out.println(it.next());
        }

關於刪除元素

有這樣一個問題,需要我們思考:在迭代時如何正確刪除元素

方式一:size()遍歷調用list.remove()刪除

        list.clear();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("a");
        list.add("a");
         /**
         * 當remove()一個元素後,後面的的元素會集體向前移動,這樣刪除掉的元素的下一個
         * 元素會移動到當前位置,但此位置已經循環過了,所以會漏掉該元素的循環,log如下:
         *init[a, b, b, b, a],i=0
         * init[a, b, b, b, a],i=1
         * after delete:[a, b, b, a]
         * init[a, b, b, a],i=2
         * after delete:[a, b, a]
         * result:[a, b, a]
         */
        for(int i=0;i<list.size();i++){
            System.out.println("init"+list.toString()+",i="+i);
            if(list.get(i).equals("b")){
                //list.remove(i); 與list.remove(obj)同效果
                list.remove("b");
                System.out.println("after delete:"+list.toString());
            }
        }

方式二:迭代器遍歷調用list.remove()

ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        }

結果:
報ConcurrentModificationException()錯誤。因爲在list.remove()時會修改modCount()值,如下:

    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;
    }

modCount就和創建Iterator()時值不相等,那麼再次調用Itr.next()時進行checkForComodification()檢查,就會報錯。可以再回頭看下next()代碼

方式三:通過Itr.remove()方式刪除

        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                iterator.remove();   //注意這個地方
        }

正常刪除,因爲在Itr.remove()時會更新expectedModCount值

後記

Java中的集合(Collection Map)中,每一個集合都實現了自己的迭代器,這樣外部訪問時可以採用統一的接口。而實現卻是每一個集合中不同的實現。這種模式值得我們在開發中學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章