Iterator設計模式 給jdk寫註釋系列之jdk1.6容器(3)

前面講了兩種List,一種基於數組實現的ArrayList,一種基於鏈表實現的LinkedList,這兩種list是我們工作中最常用到的List容器。當然數組和鏈表也是兩種常見的基本數據結構,其他基本數據結構還有堆棧、隊列、樹等,對java容器的學習,也可以看做是對數據結構的學習和使用。
 
     在ArrayList和LinkedList的分析中,都沒有對容器遍歷進行分析,前面說過迭代器相對獨立和複雜,而且它又是一種非常經典的設計模式(說經典不過使用場景也就這一個...),這裏開始對Iterator進行分析。
     細心的童鞋會看到這邊blog的標題叫做"設計模式"而不是前面的"源碼解析",是的這裏會從設計模式層面出發,更偏重於怎麼更好的進行與改善代碼的設計。
 
1.統一接口
 
     先想一個問題,現在有ArrayList和LinkedList兩種容器,我們怎麼實現容器的可替換性呢?什麼叫做可替換性,就是我底層的容器實現可以隨意改變,而對用戶的使用不產生任何影響,說到這裏應該都明白了就是要統一接口,用戶只需要面向接口編程對不對(這裏以及下面說的用戶都是指開發者)。來看看ArrayList和LinkedList是不是這樣做的(假裝你還不知道的情況下,一起看吧^_^)?
 
  ArrayList的定義:
1 public class ArrayList<E> extends AbstractList<E>
2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

  LinkedList的定義:

1 public class LinkedList<E>
2           extends AbstractSequentialList<E>
3           implements List<E>, Deque<E>, Cloneable, java.io.Serializable

 

   從定義上可以看到ArrayList和LinkedList都實現了List接口,ok看下List接口的定義:

複製代碼
 1 public interface List<E> extends Collection<E> {
 2          int size();
 3          boolean isEmpty();
 4          boolean contains(Object o);
 5          Iterator<E> iterator();
 6          Object[] toArray();
 7          <T> T[] toArray(T[] a);
 8          boolean add(E e);
 9          boolean remove(Object o);
10          boolean containsAll(Collection<?> c);
11          boolean addAll(Collection<? extends E> c);
12          boolean addAll( int index, Collection<? extends E> c);
13          boolean removeAll(Collection<?> c);
14          boolean retainAll(Collection<?> c);
15          void clear();
16          boolean equals(Object o);
17          int hashCode();
18          E get( int index);
19          E set( int index, E element);
20          void add( int index, E element);
21          E remove( int index);
22          int indexOf(Object o);
23          int lastIndexOf(Object o);
24          ListIterator<E> listIterator();
25          ListIterator<E> listIterator( int index);
26          List<E> subList( int fromIndex, int toIndex);
27 }
複製代碼

  可以看到List中對容器的各種操作add、remove、set、get、size等進行了統一定義,同時List實現了Collection接口,繼續看下Collection接口的定義(先不關心Iterator):

複製代碼
 1 public interface Collection<E> extends Iterable<E> {
 2           int size();
 3     boolean isEmpty();
 4     boolean contains(Object o);
 5     Iterator<E> iterator();
 6     Object[] toArray();
 7     <T> T[] toArray(T[] a);
 8     boolean add(E e);
 9     boolean remove(Object o);
10     boolean containsAll(Collection<?> c);
11     boolean addAll(Collection<? extends E> c);
12     boolean removeAll(Collection<?> c);
13     boolean retainAll(Collection<?> c);
14     void clear();
15     boolean equals(Object o);
16     int hashCode();
17 }
複製代碼

  

  有了這兩個接口,對於ArrayList和LinkeList的操作是不是就可以這麼寫了呢?

1 Collection<String> collection = new ArrayList<String>();
2 collection.add("hello");
3 collection.add("java");
4 collection.remove("hello");

  對於ArrayList的實現不滿意,ok換成LinkedList實現,

1 Collection<String> collection = new LinkedList<String>();
2 collection.add("hello");
3 collection.add("java");
4 collection.remove("hello");

  對於用戶來說,add、remove等操作是沒有任何影響的,好了,到這裏瞭解了統一接口,面向接口編程的好處,接下來在思考另外一個問題,怎麼給容器提供一種遍歷方式。

 

2.Iterator設計思想
 
     你可能會認爲不就是遍歷嘛,很好提供啊,ArrayList就用數組下標進行遍歷,LinkedList就用指針next進行遍歷,仔細想一下真的這麼簡單嘛?結合上一個問題,如果底層的容器實現變化了,用戶的使用是不是也需要根據具體實現的數據結構來改變遍歷方式呢?顯然這樣是不科學的嗎,這麼簡單我講上面的統一接口乾嘛,對不對。。。
     
     是的,需要統一一種遍歷方式,提供一個統計遍歷接口,而具體實現需要具體的容器自己根據自身數據結構特點來完成,而對於用戶就只有一個接口而已,萬年不變。於是就有了Iterator,接下來看下Iterator的實現吧。。。
     先回過頭去看Collection的定義,發現Collection 實現了一個Iterable 接口(早就發現了好嘛?),來看下的Iterable的定義,
複製代碼
 1 /** Implementing this interface allows an object to be the target of
 2  *  the "foreach" statement.
 3  * @since 1.5
 4  */
 5 public interface Iterable<T> {
 6 
 7     /**
 8      * Returns an iterator over a set of elements of type T.
 9      *
10      * @return an Iterator.
11      */
12     Iterator<T> iterator();
13 }
複製代碼

  英文註釋說,實現iterable接口的類可以使用“foreach”操作,然後並要求實現Iterator<T> iterator()方法,該方法返回一個Iterator接口對象,來看下Iterator接口,

複製代碼
1 public interface Iterator<E> {
2     // 是否還有元素
3     boolean hasNext();
4     // 下一個元素
5     E next(); 
6     // 將迭代器返回的元素刪除
7     void remove();
8 }
複製代碼

  Iterator接口一共有三個方法,通過這三個方法就可以實現通用遍歷了,ok,我們來試一下。

複製代碼
1 Collection<String> collection = new ArrayList<String>();
2 collection.add("hello");
3 collection.add("java");
4 
5 Iterator<String> iterator = collection.iterator();
6 while (iterator.hasNext()) {
7      System. out.println(iterator.next());
8 }
複製代碼
  用戶再也不用關心容器的底層實現了,不管你是數組還是鏈表還是其他什麼,只需要用Iterator就可以進行遍歷了。
     
     到這裏Iterator的設計原則和思路實際上已經講完了,我們來整理下Iterator模式的幾個角色:
     1.迭代器Iterator接口。
     2.迭代器Iterator的實現類,要求重寫hasNext(),next(),remove()三個方法
     3.容器統一接口,要求有一個返回Iterator接口的方法。
     4.容器實現類,實現一個返回Iterator接口的方法。

 

3.自己實現容器的Iterator遍歷
 
     如果讓我們自己來實現Iterator模式,應該怎麼來實現呢,試一下。
  Iterator接口:
1 public interface MyIterator {
2        Object next();
3         boolean hasNext();
4 }

  容器統一接口:

1 public interface MyCollection {
2         void add(Object o);
3         int size();
4        MyIterator iterator();
5 }

  容器實現類和Iterator實現類(Iterator實現類爲容器內部類):

複製代碼
 1 public class MyArrayList implements MyCollection {
 2 
 3         private Object[] data ;
 4         private int size;
 5 
 6         public MyArrayList() {
 7                data = new Object[10];
 8        }
 9 
10         public void add(Object o) {
11                if (size == data. length) {
12                      Object[] newData = new Object[data .length * 2];
13                      System. arraycopy(data, 0, newData, 0, data.length     );
14                       data = newData;
15               }
16                data[size ] = o;
17                size++;
18        }
19 
20         public int size() {
21                return size ;
22        }
23 
24         @Override
25         public MyIterator iterator() {
26                return new Itr();
27        }
28 
29         private class Itr implements MyIterator {
30                private int index = 0;
31 
32                @Override
33                public boolean hasNext() {
34                       if (index >= size) {
35                             return false;
36                      } else {
37                             return true;
38                      }
39               }
40 
41                @Override
42                public Object next() {
43                      Object o = data[index ];
44                       index++;
45                       return o;
46               }
47 
48        }
49 }
複製代碼

  應用一下:

複製代碼
 1 public class Test {
 2 
 3         public static void main(String[] args) {
 4               MyCollection c = new MyArrayList();
 5               c.add( "t");
 6               c.add( "s");
 7               c.add( "t");
 8               c.add( "d");
 9 
10               System. out.println(c.size());
11 
12               MyIterator itr = c.iterator();
13                while (itr.hasNext()) {
14                      System. out.println(itr.next());
15               }
16        }
17 
18 }
複製代碼

  看看結果:

4
t
s
t
d

  沒問題,很容易就實現了對不對,當然這裏只是簡單模擬,沒有過多的追求效率和優雅的設計。

 

4.ArrayList的Iterator實現
 
     下面來看一看ArrayList和LinkedList對Iterator接口的實現吧,ArrayList的Iterator實現封裝在AbstractList中,看一下它的實現:
1 public Iterator<E> iterator() {
2              return new Itr();
3       }

  

  和自己實現的一樣,採用內部類實現 Iterator接口。

複製代碼
 1 private class Itr implements Iterator<E> {
 2        // 將要next返回元素的索引
 3         int cursor = 0;
 4        // 當前返回的元素的索引,初始值-1
 5         int lastRet = -1;
 6 
 7         /**
 8         * The modCount value that the iterator believes that the backing
 9         * List should have.  If this expectation is violated, the iterator
10         * has detected concurrent modification.
11         */
12         int expectedModCount = modCount;
13 
14         public boolean hasNext() {
15             // 由於cursor是將要返回元素的索引,也就是下一個元素的索引,和size比較是否相等,也就是判斷是否已經next到最後一個元素
16             return cursor != size();
17        }
18 
19         public E next() {
20             checkForComodification();
21            try {
22               // 根據下一個元素索引取出對應元素
23               E next = get( cursor);
24               // 更新lastRet爲當前元素的索引,cursor加1
25                lastRet = cursor ++;
26               // 返回元素
27                return next;
28            } catch (IndexOutOfBoundsException e) {
29               checkForComodification();
30                throw new NoSuchElementException();
31            }
32        }
33 
34         public void remove() {
35            // remove前必須先next一下,先取得當前元素
36            if (lastRet == -1)
37                throw new IllegalStateException();
38             checkForComodification();
39 
40            try {
41               AbstractList. this.remove(lastRet );
42                // 確保lastRet比cursor小、理論上永遠lastRet比cursor小1
43                if (lastRet < cursor)
44                   // 由於刪除了一個元素cursor回退1
45                   cursor--;
46                // 重置爲-1
47                lastRet = -1;
48                expectedModCount = modCount ;
49            } catch (IndexOutOfBoundsException e) {
50                throw new ConcurrentModificationException();
51            }
52        }
53 
54         final void checkForComodification() {
55            if (modCount != expectedModCount)
56                throw new ConcurrentModificationException();
57        }
58 }
複製代碼
  ArrayList的Iterator實現起來和我們自己實現的大同小異,很好理解。
     有一點需要指出的是這裏的checkForComodification 檢查,之前ArrayList中留了個懸念,modCount這個變量到底是做什麼的,這裏簡單解釋一下:迭代器Iterator允許在容器遍歷的時候對元素進行刪除,但是有一點問題那就是當多線程操作容器的時候,在用Iterator對容器遍歷的同時,其他線程可能已經改變了該容器的內容(add、remove等操作),所以每次對容器內容的更改操作(add、remove等)都會對modCount+1,這樣相當於樂觀鎖的版本號+1,當在Iterator遍歷中remove時,檢查到modCount發生了變化,則馬上拋出異常ConcurrentModificationException,這就是Java容器中的“fail-fast”機制
 
5.LinkedList的Iterator實現
 
     LinkedList的Iterator實現定義在AbstracSequentialtList中,實現在AbstractList中,看一下:
     AbstracSequentialtList的定義:
1 public Iterator<E> iterator() {
2         return listIterator();
3     }

  AbstractList的實現:

複製代碼
1 public ListIterator<E> listIterator() {
2         return listIterator(0);
3     }
4    public ListIterator<E> listIterator(final int index) {
5         if (index<0 || index>size())
6          throw new IndexOutOfBoundsException( "Index: "+index);
7 
8         return new ListItr(index);
9     }
複製代碼

  看一下ListItr實現:

複製代碼
  1 private class ListItr implements ListIterator<E> {
  2         // 最後一次返回的節點,默認位header節點
  3         private Entry<E> lastReturned = header;
  4         // 將要返回的節點
  5         private Entry<E> next ;
  6         // 將要返回的節點index索引
  7         private int nextIndex;
  8         private int expectedModCount = modCount;
  9 
 10        ListItr( int index) {
 11            // 索引越界檢查
 12            if (index < 0 || index > size)
 13                throw new IndexOutOfBoundsException( "Index: "+index+
 14                                              ", Size: "+size );
 15            // 簡單二分,判斷遍歷的方向
 16            if (index < (size >> 1)) {
 17                // 取得index位置對應的節點
 18                next = header .next;
 19                for (nextIndex =0; nextIndex<index; nextIndex++)
 20                   next = next .next;
 21            } else {
 22                next = header ;
 23                for (nextIndex =size; nextIndex>index; nextIndex --)
 24                   next = next .previous;
 25            }
 26        }
 27 
 28         public boolean hasNext() {
 29            // 根據下一個節點index是否等於size,判斷是否有下一個節點
 30            return nextIndex != size;
 31        }
 32 
 33         public E next() {
 34            checkForComodification();
 35            // 遍歷完成
 36            if (nextIndex == size)
 37                throw new NoSuchElementException();
 38 
 39            // 賦值最近一次返回的節點
 40            lastReturned = next ;
 41            // 賦值下一次要返回的節點(next後移)
 42            next = next .next;
 43            // 將要返回的節點index索引+1
 44            nextIndex++;
 45            return lastReturned .element;
 46        }
 47 
 48         public boolean hasPrevious() {
 49            return nextIndex != 0;
 50        }
 51 
 52        //  返回上一個節點(雙向循環鏈表嘛、可以兩個方向遍歷)
 53         public E previous() {
 54            if (nextIndex == 0)
 55                throw new NoSuchElementException();
 56 
 57            lastReturned = next = next. previous;
 58            nextIndex--;
 59            checkForComodification();
 60            return lastReturned .element;
 61        }
 62 
 63         public int nextIndex() {
 64            return nextIndex ;
 65        }
 66 
 67         public int previousIndex() {
 68            return nextIndex -1;
 69        }
 70 
 71         public void remove() {
 72             checkForComodification();
 73             // 取出當前返回節點的下一個節點
 74             Entry<E> lastNext = lastReturned.next ;
 75             try {
 76                 LinkedList. this.remove(lastReturned );
 77             } catch (NoSuchElementException e) {
 78                 throw new IllegalStateException();
 79             }
 80            // 確認下次要返回的節點不是當前節點,如果是則修正
 81            if (next ==lastReturned)
 82                 next = lastNext;
 83             else
 84              // 由於刪除了一個節點,下次要返回的節點索引-1
 85                nextIndex--;
 86            // 重置lastReturned爲header節點
 87            lastReturned = header ;
 88            expectedModCount++;
 89        }
 90 
 91         public void set(E e) {
 92            if (lastReturned == header)
 93                throw new IllegalStateException();
 94            checkForComodification();
 95            lastReturned.element = e;
 96        }
 97 
 98         public void add(E e) {
 99            checkForComodification();
100            lastReturned = header ;
101            addBefore(e, next);
102            nextIndex++;
103            expectedModCount++;
104        }
105 
106         final void checkForComodification() {
107            if (modCount != expectedModCount)
108                throw new ConcurrentModificationException();
109        }
110 }
複製代碼

 

  LinkedList的Iterator實現就分析完了,從這裏可以看出數組和鏈表的遍歷方式不同,但對於用戶統一使用Iterator迭代器進行遍歷器,做到只需面對接口編程,這也是Iterator的最終要做到的。
 
     最後囉嗦一句,Iterator是比較簡單的一種設計模式,使用場景也僅限於此,不要糾結於Iterator的應用而是要明白其設計思想,同時要對一些常用數據結構應該要了解掌握。
 
     Iterator 完!

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