迭代器模式
提供一種方式去訪問一個容器元素中的各個對象,而又不暴露該對象的內部細節。
迭代器模式的結構
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等,這些集合都有自己的迭代器。假如我們要實現一個這樣的新的容器,當然也需要引入迭代器模式,給我們的容器實現一個迭代器。