Java中的迭代器模式


迭代器模式

提供一種方式去訪問一個容器元素中的各個對象,而又不暴露該對象的內部細節。

迭代器模式的結構

1、迭代器角色

負責定義訪問和遍歷元素的接口

2、具體迭代器角色

實現迭代器接口,並要記錄遍歷中的當前位置

3、容器角色

負責提供創建具體迭代器角色的接口

4、具體容器角色

實現創建具體迭代器角色的接口,這個具體迭代器角色與該容器的結構相關

為什么需要迭代器模式

列舉一個簡單的示例,遍歷ArrayList、LinkedList、HashSet中各個元素:

 1 public static void main(String[] args) {
 2         List<Integer> arrayList = new ArrayList<Integer>();
 3         arrayList.add(1);
 4         arrayList.add(2);
 5         List<Integer> linkedList = new LinkedList<Integer>();
 6         linkedList.add(3);
 7         linkedList.add(4);
 8         HashSet<Integer> hashSet = new HashSet<Integer>();
 9         hashSet.add(5);
10         hashSet.add(6);
11         Iterator<Integer> iterator = null;
12         iterator = arrayList.iterator();
13         System.out.println("ArrayList:");
14         while (iterator.hasNext())
15         {
16             System.out.print(iterator.next() + "\t");
17         }
18         System.out.println("\nLinkedList:");
19         iterator = linkedList.iterator();
20         while (iterator.hasNext())
21         {
22             System.out.print(iterator.next() + "\t");
23         }
24         System.out.println("\nHashSet:");
25         iterator = hashSet.iterator();
26         while (iterator.hasNext())
27         {
28             System.out.print(iterator.next() + "\t");
29         }
30     }

運行結果:

ArrayList:
1    2    
LinkedList:
3    4    
HashSet:
5    6    

隨便一個結合,是要實現了iterable接口,就可以用這樣的方式遍歷。開發者不需要知道集合中如何去遍歷的細節,只管用類似的遍歷方法就好了。

Java中的Iterable和Iterator

實現了Iterable接口,則表示某個對象是可被迭代的;Iterator接口相當於是一個迭代器,實現了Iterator接口,等於具體定義了這個可被迭代的對象時如何進行迭代的。參看Iterable接口的定義:

1 public interface Iterable<T> {
2 
3     /**
4      * Returns an iterator over a set of elements of type T.
5      * 
6      * @return an Iterator.
7      */
8     Iterator<T> iterator();
9 }
 1 public interface Iterator<E> {
 2     /**
 3      * Returns {@code true} if the iteration has more elements.
 4      * (In other words, returns {@code true} if {@link #next} would
 5      * return an element rather than throwing an exception.)
 6      *
 7      * @return {@code true} if the iteration has more elements
 8      */
 9     boolean hasNext();
10 
11     /**
12      * Returns the next element in the iteration.
13      *
14      * @return the next element in the iteration
15      * @throws NoSuchElementException if the iteration has no more elements
16      */
17     E next();
18 
19     /**
20      * Removes from the underlying collection the last element returned
21      * by this iterator (optional operation).  This method can be called
22      * only once per call to {@link #next}.  The behavior of an iterator
23      * is unspecified if the underlying collection is modified while the
24      * iteration is in progress in any way other than by calling this
25      * method.
26      *
27      * @implSpec
28      * The default implementation throws an instance of
29      * {@link UnsupportedOperationException} and performs no other action.
30      *
31      * @throws UnsupportedOperationException if the {@code remove}
32      *         operation is not supported by this iterator
33      *
34      * @throws IllegalStateException if the {@code next} method has not
35      *         yet been called, or the {@code remove} method has already
36      *         been called after the last call to the {@code next}
37      *         method
38      */
39     default void remove() {
40         throw new UnsupportedOperationException("remove");
41     }
42 
43     /**
44      * Performs the given action for each remaining element until all elements
45      * have been processed or the action throws an exception.  Actions are
46      * performed in the order of iteration, if that order is specified.
47      * Exceptions thrown by the action are relayed to the caller.
48      *
49      * @implSpec
50      * <p>The default implementation behaves as if:
51      * <pre>{@code
52      *     while (hasNext())
53      *         action.accept(next());
54      * }</pre>
55      *
56      * @param action The action to be performed for each element
57      * @throws NullPointerException if the specified action is null
58      * @since 1.8
59      */
60     default void forEachRemaining(Consumer<? super E> action) {
61         Objects.requireNonNull(action);
62         while (hasNext())
63             action.accept(next());
64     }
65 }

 為什么一定要實現Iterable接口而不是直接實現Iterator接口?

Iterator接口的核心方法next()和hasNext()依賴於迭代器的當前迭代位置。如果直接實現Iterator接口,那么集合對象中就包含當前迭代位置的數據。集合在不同方法間被傳遞時,由於當前迭代位置不可預置,那么next()方法的結果會變成不可預知的。除非再為Iterator接口添加一個reset()方法,用來重置當前迭代位置。但即使這樣,Collection也同時只能存在一個當前迭代位置。而Iterable,每次調用都返回一個從頭開始計數的迭代器,多個迭代器時互不干擾。

 

 1 public class ArrayList<E> implements List<E>, Iterator<E>, RandomAccess, Cloneable, Serializable
 2 {
 3     /**
 4      * 序列化ID
 5      */
 6     private static final long serialVersionUID = -5786598508477165970L;
 7     
 8     private int size = 0;
 9     private transient Object[] elementData = null;
10     
11     public E next()
12     {
13         ...
14     }
15     
16     public boolean hasNext()
17     {
18         ...
19     }
20     ...
21 }

這么問題就來了,如果一個ArrayList實例被多個地方迭代,next()方法、hasNext()直接操作的是ArrayList中的資源,假如我在ArrayList中定義一個迭代位置的變量,那么對於不同調用處,這個迭代變量是共享的,線程A迭代的時候將迭代變量設置成了第5個位置,這時候切換到了線程B,對於線程B來講,就從第5個位置開始遍歷此ArrayList了,根本不是從0開始,如何正確迭代?

 1 public class ArrayListIterator implements Iterator<E>
 2 {
 3     int iteratorPostion = 0;
 4     
 5     /**
 6      * 判斷是否后面還有元素
 7      */
 8     @Override
 9     public boolean hasNext()
10     {
11         if ((iteratorPostion + 1) > size)
12             return false;
13         return true;
14     }
15 
16     /**
17      * 返回之前一個元素的引用
18      */
19     @Override
20     public E next()
21     {
22         return (E)elementData[iteratorPostion++];
23     }
24     ...
25 } 

每次都返回一個返回一個ArrayListIterator實例出去:

1 /**
2  * 返回一個ArrayList的迭代器,可以通過該迭代器遍歷ArrayList中的元素
3  */
4 public Iterator<E> iterator()
5 {
6     return new ArrayListIterator();
7 }

這就保證了,即使是多處同時迭代這個ArrayList,依然每處都是從0開始迭代這個ArrayList實例的。

迭代器模式的優缺點

  迭代器模式的優點:

  • 簡化了遍歷方式,對於對象集合的遍歷,還是比較麻煩的,對於數組或者有序列表,我們尚可以通過游標來取得,但用戶需要在對集合了解很清楚的前提下,自行遍歷對象,但是對於hash表來說,用戶遍歷起來就比較麻煩了。而引入了迭代器方法后,用戶用起來就簡單的多了。
  • 可以提供多種遍歷方式,比如說對有序列表,我們可以根據需要提供正序遍歷,倒序遍歷兩種迭代器,用戶用起來只需要得到我們實現好的迭代器,就可以方便的對集合進行遍歷了。
  • 封裝性良好,用戶只需要得到迭代器就可以遍歷,而對於遍歷算法則不用去關心。

  迭代器模式的缺點:

  • 對於比較簡單的遍歷(像數組或者有序列表),使用迭代器方式遍歷較為繁瑣,大家可能都有感覺,像ArrayList,我們寧可願意使用for循環和get方法來遍歷集合。

 總的來說: 迭代器模式是與集合共生共死的,一般來說,我們只要實現一個集合,就需要同時提供這個集合的迭代器,就像java中的Collection,List、Set、Map等,這些集合都有自己的迭代器。假如我們要實現一個這樣的新的容器,當然也需要引入迭代器模式,給我們的容器實現一個迭代器。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM