Java 中iterator和iterable的關係是怎樣的

迭代器模式

在容器裏存放了大量的同類型對象,我們如果想逐個訪問容器中的對象,就必須要知道容器的結構。比如,ArrayList 和 LinkedList 的遍歷方法一定是不一樣的,如果再加上HashMap, TreeMap,以及我們現在正在研究的BinarySearchTree,對於容器的使用者而言,肯定是一個巨大的負擔。作爲容器的使用者,我肯定希望所有的容器能提供一個統一的接口,這個接口可以遍歷容器中的所有內容,又可以把容器的細節屏蔽掉。

Iterator上定義了各種方法,用於遍歷。

例如:

    public static void main(String args[]) {
        LinkedList<Integer> ll = new LinkedList<>();
        for (int i = 0; i < 10; i++)
            ll.add(i);

        Iterator<Integer> ii = ll.iterator();
        while (ii.hasNext())
            System.out.printf("%d\t",ii.next());


        System.out.println();
        ArrayList<Integer> al = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            ll.add(i);

        Iterator<Integer> aii = al.iterator();
        while (aii.hasNext())
            System.out.printf("%d\t",aii.next());
    }

可以看到,我們根本就不需要關心後面具體的數據結構,只需要使用 Iterator,不管後面是數組還是鏈表,都可以很方便地遍歷整個容器。

接口定義:

public interface Iterable<T> {
  Iterator<T> iterator();
}

public interface Iterator<E> {
  boolean hasNext();
  E next();  // 取出下一個元素
  void remove();
}



用Iterator模式實現遍歷集合

Iterator模式是用於遍歷集合類的標準訪問方法。它可以把訪問邏輯從不同類型的集合類中抽象出來,從而避免向客戶端暴露集合的內部結構。

例如,如果沒有使用Iterator,遍歷一個數組的方法是使用索引:、

  for(int i=0; i<array.size(); i++) { ... get(i) ... }

而訪問一個鏈表(LinkedList)又必須使用while循環:

while((e=e.next())!=null) { ... e.data() ... } 

以上兩種方法客戶端都必須事先知道集合的內部結構,訪問代碼和集合本身是緊耦合,無法將訪問邏輯從集合類和客戶端代碼中分離出來,每一種集合對應一種遍歷方法,客戶端代碼無法複用。

更恐怖的是,如果以後需要把ArrayList更換爲LinkedList,則原來的客戶端代碼必須全部重寫。

解決以上問題,Iterator模式總是用同一種邏輯來遍歷集合:

for(Iterator it = c.iterater(); it.hasNext(); ) { ... } 

奧祕在於客戶端自身不維護遍歷集合的"指針",所有的內部狀態(如當前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過工廠方法生成,因此,它知道如何遍歷整個集合。

客戶端從不直接和集合類打交道,它總是控制Iterator,向它發送"向前",“向後”,"取當前元素"的命令,就可以間接遍歷整個集合。

首先看看Java.util.Iterator接口的定義: 
public interface Iterator<E> {
  boolean hasNext();
  E next();  // 取出下一個元素
  void remove();
}

依賴前兩個方法就能完成遍歷,典型的代碼如下:

	 for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 對o的操作... } 

每一種集合類返回的Iterator具體類型可能不同,Array可能返回ArrayIterator,Set可能返回 SetIterator,Tree可能返回TreeIterator,但是它們都實現了Iterator接口,因此,客戶端不關心到底是哪種 Iterator,它只需要獲得這個Iterator接口即可,這就是面向對象的威力。

所有集合類都實現了 Collection 接口,而 Collection 繼承了 Iterable 接口。

public interface Collection<E> extends Iterable<E> {  }

而在具體的實現類中(比如ArrayList),則在內部維護了一個Ite內部類,該類

/**
     * An optimized version of AbstractList.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();
            }
        }

在遍歷元素或者刪除元素時,必須要進行next()方法,要不然,迭代器不能正常運行:如

/**
 *太極雲軟件技術股份有限公司版權所有 1990-2016. http://www.tyky.com.cn
 * @file com.hoojjack.spring_study.tutorialspoint
 *
 */
package com.hoojjack.spring_study.tutorialspoint;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @ClassName: TestIterator
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author hoojjack
 * @date 2017年5月15日 下午6:42:15
 *
 */
public class TestIterator {
    public static void main(String args[]){
        List<String> list = new ArrayList<String>();
        list.add("h");
        list.add("o");
        list.add("o");
        list.add("j");
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            //it.next();//註釋這行會報錯,具體原因請看源碼
            it.remove();
            break;
        }
        for(String i:list){
            System.out.println(i);
        }
    }
}

爲什麼一定要去實現Iterable這個接口呢?爲什麼不直接實現Iterator接口呢?

  看一下JDK中的集合類,比如List一族或者Set一族,都是實現了Iterable接口,但並不直接實現Iterator接口。 仔細想一下這麼做是有道理的。

因爲iIterator接口的核心方法next()或者hasNext()是依賴於迭代器的當前迭代位置的。如果Collection直接實現Iterator接口,勢必導致集合隊中中包含當前迭代位置的數據(指針)。當集合在不同方法間被傳遞時,由於當前迭代位置不可預置,那麼next()方法的結果會變成不可預知。除非再爲Iterator接口添加一個reset()方法,用來重置當前迭代位置。 但即時這樣,Collection也只能同時存在一個當前迭代位置。 而Iterable則不然,每次調用都會返回一個從頭開始計數的迭代器。 多個迭代器是互不干擾的。

直接繼承Iterator,從Iterator接口可以看出需要子類實現next(),hasNext()方法,假設都交給List去實現,那麼獲取list的Iterator()時就會出現上面解釋那樣,而繼承Iterable接口,則需要每次List返回一個新的Iterator對象(見ArrayList的new Itr()),因此可以避免上述說到的原因。

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