ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

容器中的Iterator设计模式

2021-11-11 12:05:19  阅读:81  来源: 互联网

标签:容器 Iterator index int next boolean 设计模式 public


目录

1 Iterator设计模式

由于ArrayListLinkedList这两种list是我们工作中最常用到的List容器。当然数组和链表也是两种常见的基本数据结构,其他基本数据结构还有堆栈、队列、树等,对java容器的学习,也可以看做是对数据结构的学习和使用。

ArrayListLinkedList的分析中,都没有对容器遍历进行分析,前面说过迭代器相对独立和复杂,而且它又是一种非常经典的设计模式(说经典不过使用场景也就这一个...),这里开始对Iterator进行分析。

1.1统一接口

先想一个问题,现在有ArrayListLinkedList两种容器,我们怎么实现容器的可替换性呢?什么叫做可替换性,就是底层的容器实现可以随意改变,而对用户的使用不产生任何影响,说到这里应该都明白了就是要统一接口,用户只需要面向接口编程,来看看ArrayListLinkedList是不是这样做的

  1. ArrayList的定义:
 public class ArrayList<E> extends AbstractList<E>
         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. LinkedList的定义:
 public class LinkedList<E>
           extends AbstractSequentialList<E>
           implements List<E>, Deque<E>, Cloneable, java.io.Serializable

从定义上可以看到ArrayListLinkedList都实现了List接口,List接口的定义:

  public interface List<E> extends Collection<E> {
           int size();
           boolean isEmpty();
           boolean contains(Object o);
          Iterator<E> iterator();
           Object[] toArray();
           <T> T[] toArray(T[] a);
           boolean add(E e);
           boolean remove(Object o);
          boolean containsAll(Collection<?> c);
          boolean addAll(Collection<? extends E> c);
          boolean addAll( int index, Collection<? extends E> c);
          boolean removeAll(Collection<?> c);
          boolean retainAll(Collection<?> c);
          void clear();
          boolean equals(Object o);
          int hashCode();
          E get( int index);
          E set( int index, E element);
          void add( int index, E element);
          E remove( int index);
          int indexOf(Object o);
          int lastIndexOf(Object o);
          ListIterator<E> listIterator();
          ListIterator<E> listIterator( int index);
          List<E> subList( int fromIndex, int toIndex);
}

可以看到List中对容器的各种操作add、remove、set、get、size等进行了统一定义,同时List实现了Collection接口,继续看下Collection接口的定义(先不关心Iterator):

  public interface Collection<E> extends Iterable<E> {
            int size();
      boolean isEmpty();
      boolean contains(Object o);
     Iterator<E> iterator();
      Object[] toArray();
      <T> T[] toArray(T[] a);
      boolean add(E e);
      boolean remove(Object o);
     boolean containsAll(Collection<?> c);
     boolean addAll(Collection<? extends E> c);
     boolean removeAll(Collection<?> c);
     boolean retainAll(Collection<?> c);
     void clear();
     boolean equals(Object o);
     int hashCode();
 }

  
有了这两个接口,对于ArrayListLinkeList的操作是不是就可以这么写了呢?

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

对于ArrayList的实现不满意,那么换成LinkedList实现,

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

对于用户来说,add、remove等操作是没有任何影响的,好了,到这里了解了统一接口,面向接口编程的好处,接下来在思考另外一个问题,怎么给容器提供一种遍历方式。

1.2 Iterator设计思想

可能会认为不就是遍历嘛,很好提供啊,ArrayList就用数组下标进行遍历,LinkedList就用指针next进行遍历,仔细想一下真的这么简单嘛?结合上一个问题,如果底层的容器实现变化了,用户的使用是不是也需要根据具体实现的数据结构来改变遍历方式呢?显然这样是不科学的

是的,需要统一一种遍历方式,提供一个统计遍历接口,而具体实现需要具体的容器自己根据自身数据结构特点来完成,而对于用户就只有一个接口而已,万年不变。于是就有了Iterator,接下来看下Iterator的实现
先回过头去看Collection的定义,发现Collection 实现了一个Iterable 接口,来看下的Iterable的定义,

  /** Implementing this interface allows an object to be the target of
   *  the "foreach" statement.
   * @since 1.5
   */ 
    public interface Iterable<T> {
  
      /**       * Returns an iterator over a set of elements of type T.
       *
      * @return an Iterator.
      */
           Iterator<T> iterator();
 }

英文注释说,实现iterable接口的类可以使用foreach操作,然后并要求实现Iterator<T> iterator()方法,该方法返回一个Iterator接口对象,来看下Iterator接口,

 public interface Iterator<E> {
     // 是否还有元素
          boolean hasNext();
     // 下一个元素
          E next(); 
     // 将迭代器返回的元素删除
          void remove();
 }

Iterator接口一共有三个方法,通过这三个方法就可以实现通用遍历了

 Collection<String> collection = new ArrayList<String>();
 collection.add("hello");
 collection.add("java");
 
 Iterator<String> iterator = collection.iterator();
 while (iterator.hasNext()) {
      System. out.println(iterator.next());
 }

用户再也不用关心容器的底层实现了,不管你是数组还是链表还是其他什么,只需要用Iterator就可以进行遍历了。

到这里Iterator的设计原则和思路实际上已经讲完了,我们来整理下Iterator模式的几个角色:

  1. 迭代器Iterator接口。
  2. 迭代器Iterator的实现类,要求重写hasNext(),next(),remove()三个方法 。
  3. 容器统一接口,要求有一个返回Iterator接口的方法。
  4. 容器实现类,实现一个返回Iterator接口的方法。

1.3 自己实现容器的Iterator遍历

如果让我们自己来实现Iterator模式,应该怎么来实现呢,试一下。
Iterator接口:

 public interface MyIterator {
        Object next();
         boolean hasNext();
}

容器统一接口:

 public interface MyCollection {
         void add(Object o);
         int size();
        MyIterator iterator();
 }

容器实现类和Iterator实现类(Iterator实现类为容器内部类):

 public class MyArrayList implements MyCollection {
  
          private Object[] data ;
          private int size;
  
          public MyArrayList() {
                 data = new Object[10];
         }
  
         public void add(Object o) {
                if (size == data. length) {
                      Object[] newData = new Object[data .length * 2];
                      System. arraycopy(data, 0, newData, 0, data.length     );
                       data = newData;
               }
                data[size ] = o;
                size++;
       }
 
         public int size() {
                return size ;
        }
 
         @Override
         public MyIterator iterator() {
                return new Itr();
        }
 
         private class Itr implements MyIterator {
                private int index = 0;
 
                @Override
                public boolean hasNext() {
                       if (index >= size) {
                             return false;
                      } else {
                             return true;
                      }
               }
 
              @Override
              public Object next() {
                     Object o = data[index ];
                       index++;
                      return o;
              }
 
        }
 }

应用一下:

  public class Test {
  
          public static void main(String[] args) {
                MyCollection c = new MyArrayList();
                c.add( "t");
                c.add( "s");
                c.add( "t");
                c.add( "d");
  
               System. out.println(c.size());
 
               MyIterator itr = c.iterator();
                while (itr.hasNext()) {
                      System. out.println(itr.next());
               }
        }
 
}

看看结果:
4
t
s
t
d

没问题,很容易就实现了对不对,当然这里只是简单模拟,没有过多的追求效率和优雅的设计。

1.4 ArrayList的Iterator实现

下面来看一看ArrayListLinkedListIterator接口的实现吧,ArrayListIterator实现封装在AbstractList中,看一下它的实现:

 public Iterator<E> iterator() {
              return new Itr();
       }

和自己实现的一样,采用内部类实现 Iterator接口。

  private class Itr implements Iterator<E> {
         // 将要next返回元素的索引
                  int cursor = 0;
         // 当前返回的元素的索引,初始值-1 5         int lastRet = -1;
  
          /**          * The modCount value that the iterator believes that the backing
          * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
                  int expectedModCount = modCount;
 
         public boolean hasNext() {
             // 由于cursor是将要返回元素的索引,也就是下一个元素的索引,和size比较是否相等,也就是判断是否已经next到最后一个元素
                          return cursor != size();
        }
 
         public E next() {
             checkForComodification();
            try {
               // 根据下一个元素索引取出对应元素
                              E next = get( cursor);
               // 更新lastRet为当前元素的索引,cursor加1
                               lastRet = cursor ++;
               // 返回元素
                               return next;
            } catch (IndexOutOfBoundsException e) {
               checkForComodification();
                throw new NoSuchElementException();
            }
        }
 
         public void remove() {
            // remove前必须先next一下,先取得当前元素
                        if (lastRet == -1)
                throw new IllegalStateException();
             checkForComodification();
 
            try {
               AbstractList. this.remove(lastRet );
                // 确保lastRet比cursor小、理论上永远lastRet比cursor小1
                                if (lastRet < cursor)
                   // 由于删除了一个元素cursor回退1
                                      cursor--;
                // 重置为-1
                                lastRet = -1;
                expectedModCount = modCount ;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
 
         final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}

ArrayListIterator实现起来和我们自己实现的大同小异,很好理解。
有一点需要指出的是这里的checkForComodification 检查,modCount这个变量到底是做什么的,这里简单解释一下:
迭代器Iterator允许在容器遍历的时候对元素进行删除,但是有一点问题那就是当多线程操作容器的时候,在用Iterator对容器遍历的同时,其他线程可能已经改变了该容器的内容(add、remove等操作),所以每次对容器内容的更改操作(add、remove等)都会对modCount+1,这样相当于乐观锁的版本号+1,当在Iterator遍历中remove时,检查到modCount发生了变化,则马上抛出异常ConcurrentModificationException,这就是Java容器中的fail-fast机制。

1.5 LinkedList的Iterator实现

LinkedListIterator实现定义在AbstracSequentialtList中,实现在AbstractList中,看一下:
AbstracSequentialtList的定义:

 public Iterator<E> iterator() {
         return listIterator();
     }

AbstractList的实现:

 public ListIterator<E> listIterator() {
         return listIterator(0);
     }
    public ListIterator<E> listIterator(final int index) {
         if (index<0 || index>size())
          throw new IndexOutOfBoundsException( "Index: "+index);
 
         return new ListItr(index);
     }

看一下ListItr实现:

   private class ListItr implements ListIterator<E> {
          // 最后一次返回的节点,默认位header节点  
                   private Entry<E> lastReturned = header;
           // 将要返回的节点  
                    private Entry<E> next ;
           // 将要返回的节点index索引  
                    private int nextIndex;
           private int expectedModCount = modCount;
   
         ListItr( int index) {
             // 索引越界检查 
                         if (index < 0 || index > size)
                 throw new IndexOutOfBoundsException( "Index: "+index+ ", Size: "+size );
             // 简单二分,判断遍历的方向 
                         if (index < (size >> 1)) {
                // 取得index位置对应的节点 
                                next = header .next;
                 for (nextIndex =0; nextIndex<index; nextIndex++)
                    next = next .next;
             } else {
                 next = header ;
                 for (nextIndex =size; nextIndex>index; nextIndex --)
                    next = next .previous;
             }
         }
  
          public boolean hasNext() {
             // 根据下一个节点index是否等于size,判断是否有下一个节点 
                         return nextIndex != size;
         }
  
          public E next() {
             checkForComodification();
             // 遍历完成 
                         if (nextIndex == size)
                 throw new NoSuchElementException();
  
             // 赋值最近一次返回的节点
                         lastReturned = next ;
            // 赋值下一次要返回的节点(next后移) 
                        next = next .next;
            // 将要返回的节点index索引+1 
                        nextIndex++;
             return lastReturned .element;
         }
  
          public boolean hasPrevious() {
             return nextIndex != 0;
         }
  
         //  返回上一个节点(双向循环链表嘛、可以两个方向遍历) 
                  public E previous() {
             if (nextIndex == 0)
                 throw new NoSuchElementException();
  
             lastReturned = next = next. previous;
            nextIndex--;
            checkForComodification();
             return lastReturned .element;
        }
  
          public int nextIndex() {
             return nextIndex ;
         }
  
          public int previousIndex() {
             return nextIndex -1;
         }
  
          public void remove() {
              checkForComodification();
              // 取出当前返回节点的下一个节点 
                 Entry<E> lastNext = lastReturned.next ;
              try {
                  LinkedList. this.remove(lastReturned );
              } catch (NoSuchElementException e) {
                  throw new IllegalStateException();
              }
             // 确认下次要返回的节点不是当前节点,如果是则修正 
                         if (next ==lastReturned)
                  next = lastNext;
              else 
                            // 由于删除了一个节点,下次要返回的节点索引-1 
                            nextIndex--;
             // 重置lastReturned为header节点 87            lastReturned = header ;
             expectedModCount++;
         }
  
          public void set(E e) {
             if (lastReturned == header)
                 throw new IllegalStateException();
             checkForComodification();
             lastReturned.element = e;
         }
  
         public void add(E e) {
            checkForComodification();
            lastReturned = header ;
            addBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }
 
         final void checkForComodification() {
           if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}

LinkedListIterator实现就分析完了,从这里可以看出数组和链表的遍历方式不同,但对于用户统一使用Iterator迭代器进行遍历器,做到只需面对接口编程,这也是Iterator的最终要做到的。

Iterator是比较简单的一种设计模式,使用场景也仅限于此,不要纠结于Iterator的应用而是要明白其设计思想,同时要对一些常用数据结构应该要了解掌握

标签:容器,Iterator,index,int,next,boolean,设计模式,public
来源: https://www.cnblogs.com/jingzh/p/15539190.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有