• Java 迭代器综述


    一、摘要

      迭代器模式是与集合共生共死的。一般来说。我们仅仅要实现一个容器,就须要同一时候提供这个容器的迭代器。使用迭代器的优点是:封装容器的内部实现细节,对于不同的集合,能够提供统一的遍历方式,简化client的訪问和获取容器内数据。在此基础上。我们能够使用 Iterator 完毕对集合的遍历。此外。for 循环和foreach 语法也能够用于遍历集合类。ListIterator 是容器 List容器族特有的双向迭代器。本文要点主要包括:

    • 迭代器模式
    • Iterator 迭代器 与 Iterable 接口
    • 循环遍历 : foreach,Iterator,for 的异同
    • ListIterator 简述(容器 List 详细解释

    二、迭代器模式

      迭代器模式是与集合共生共死的。一般来说,我们仅仅要实现一个容器。就须要同一时候提供这个容器的迭代器。就像 Java 中的 Collection (List、Set 等) 。这些容器都有自己的迭代器。假如我们要实现一个新的容器。当然也须要引入迭代器模式,给我们的容器实现一个迭代器。使用迭代器的优点是:封装容器的内部实现细节。对于不同的集合,能够提供统一的遍历方式,简化client的訪问和获取容器内数据。

      可是,由于容器与迭代器的关系太密切了,所以大多数语言在实现容器的同一时候也提供了对应的迭代器,并且在绝大多数情况下。这些语言所提供的容器和迭代器都能够满足我们的须要。所以。现实中须要我们自己去实现迭代器模式的场景还是比較少见的,我们经常仅仅须要使用语言中已有的容器和迭代器就能够了。


    1、定义与结构

    • 定义

        迭代器(Iterator)模式。又叫做游标(Cursor)模式。

      GOF给出的定义为:提供一种方法訪问一个容器(container)对象中的各个元素,而又不需暴露该容器对象的内部细节。

        从定义可见,迭代器模式是为容器而生。我们知道,对容器对象的訪问必定涉及到遍历算法。

      你能够一股脑的将遍历方法塞到容器对象中去,或者,根本不去提供什么遍历算法。让使用容器的人自己去实现。

      这两种情况好像都能够解决这个问题。然而,对于前一种情况,容器承受了过多的功能。它不仅要负责自己“容器”内的元素维护(增、删、改、查 等)。并且还要提供遍历自身的接口。并且最重要的是。 由于遍历状态保存的问题,不能对同一个容器对象同一时候进行多个遍历,并且还需添加 reset 操作

      另外一种方式倒是省事,却又将容器的内部细节暴露无遗。


    • 迭代器模式角色组成

       迭代器角色(Iterator): 迭代器角色 负责定义訪问和遍历元素的接口

       详细迭代器角色(Concrete Iterator): 详细迭代器角色 要实现迭代器接口,并要 记录遍历中的当前位置

       容器角色(Container): 容器角色 负责定义创建详细迭代器角色的接口

       详细容器角色(Concrete Container): 详细容器角色 实现创建详细迭代器角色的接口 —— 这个 详细迭代器角色 与该 容器的结构 相关


    • 结构图
                   迭代器模式结构图.jpg-53.9kB

        从结构上能够看出,迭代器模式在client与容器之间加入了迭代器角色。迭代器角色的加入。就能够非常好的避免容器内部细节的暴露,并且也使得设计符合 单一职责原则。


        
        特别须要注意的是,在迭代器模式中。详细迭代器角色和详细容器角色是耦合在一起的 —— 遍历算法是与容器的内部细节紧密相关的。为了使客户程序从与详细迭代器角色耦合的困境中脱离出来,避免详细迭代器角色的更换给客户程序带来的改动,迭代器模式抽象了详细迭代器角色,使得客户程序更具一般性和重用性,这被称为 多态迭代


    • 适用性

       1.訪问一个容器对象的内容而无需暴露它的内部表示;

       2.支持对容器对象的多种遍历;

       3.为遍历不同的容器结构提供一个统一的接口 ( 即,支持多态迭代 )。


    2、举例

      由于迭代器模式本身的规定比較松散,所以详细实现也就五花八门。我们在此仅举一例。在举例前,我们先来列举一下迭代器模式的实现方式。

    • 迭代器角色定义了遍历的接口。可是没有规定由谁来控制迭代。

      在 Java Collection 框架中,是由客户程序来控制遍历的进程,被称为 外部迭代器。另一种实现方式便是由迭代器自身来控制迭代,被称为 内部迭代器

      外部迭代器要比内部迭代器灵活、强大,并且内部迭代器在 Java 语言环境中,可用性非常弱;

    • 在迭代器模式中没有规定谁来实现遍历算法,好像理所当然的要在迭代器角色中实现。

      由于既便于一个容器上使用不同的遍历算法,也便于将一种遍历算法应用于不同的容器。可是这样就破坏掉了容器的封装 —— 容器角色就要公开自己的私有属性,在 Java 中便意味着向其它类公开了自己的私有属性;

        那我们把它放到容器角色里来实现好了,这样。迭代器角色就被架空为仅仅存放一个遍历当前位置的功能。可是遍历算法便和特定的容器紧紧绑在一起了。而在 Java Collection 框架中,提供的详细迭代器角色是定义在容器角色中的 内部类。这样便保护了容器的封装。可是同一时候容器也提供了遍历算法接口。并且你能够扩展自己的迭代器。

        我们来看下 Java Collection 中的迭代器的实现:

    //迭代器角色,仅仅定义了遍历接口
    public interface Iterator<E> {
        boolean hasNext();
        E next();
        void remove();
    }
    
    //容器角色,这里以 List 为例,间接实现了 Iterable 接口
    public interface Collection<E> extends Iterable<E> {
        ...
        Iterator<E> iterator();
        ...
    }
    public interface List<E> extends Collection<E> {}
    
    //详细容器角色,便是实现了 List 接口的 ArrayList 等类。

    为了突出重点这里指罗列和迭代器相关的内容 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { …… //这个便是负责创建详细迭代器角色的工厂方法 public Iterator<E> iterator() {  return new Itr(); } //详细迭代器角色。它是以内部类的形式出来的。 AbstractList 是为了将各个详细容器角色的公共部分提取出来而存在的。

    //作为内部类的详细迭代器角色 private class Itr implements Iterator<E> {  int cursor = 0;  int lastRet = -1; //集合迭代中的一种“高速失败”机制,这样的机制提供迭代过程中集合的安全性. ArrayList 中存在 modCount 属性,增删操作都会使 modCount++。 //通过两者的对照,迭代器能够高速的知道迭代过程中是否存在 list.add() 相似的操作。存在的话高速失败!  int expectedModCount = modCount;  public boolean hasNext() {   return cursor != size();  }  public Object next() {   checkForComodification(); //高速失败机制   try {    Object next = get(cursor);    lastRet = cursor++;    return next;   } catch(IndexOutOfBoundsException e) {    checkForComodification(); //高速失败机制    throw new NoSuchElementException();   }  }  public void remove() {   if (lastRet == -1)    throw new IllegalStateException();    checkForComodification(); //高速失败机制   try {    AbstractList.this.remove(lastRet);    if (lastRet < cursor)     cursor--;    lastRet = -1;    expectedModCount = modCount; //高速失败机制   } catch(IndexOutOfBoundsException e) {    throw new ConcurrentModificationException();   }  } //高速失败机制  final void checkForComodification() {   if (modCount != expectedModCount)    throw new ConcurrentModificationException(); //抛出异常,迭代终止  } }


    • 迭代器模式的使用

        客户程序要先得到详细容器角色,然后再通过详细容器角色得到详细迭代器角色。这样便能够使用详细迭代器角色来遍历容器了……


    3、适用情况

      我们能够看出迭代器模式给容器的应用带来以下优点:

      1) 支持以不同的方式遍历一个容器角色。依据实现方式的不同,效果上会有差别(比如。List 中的 iterator 和 listIterator)。

      2) 简化了容器的接口。

    可是在 Java Collection 中为了提高可扩展性,容器还是提供了遍历的接口。

      3) 简化了遍历方式。对于对象集合的遍历,还是比較麻烦的,对于数组或者有序列表,我们尚能够通过游标来取得,但用户须要在对集合了解非常清楚的前提下,自行遍历对象。可是对于 哈希表 来说。用户遍历起来就比較麻烦了。而引入了迭代器方法后。用户用起来就简单的多了。

      4) 能够提供多种遍历方式。比方,对于有序列表,我们能够依据须要提供正序遍历,倒序遍历两种迭代器。用户用起来仅仅须要得到我们实现好的迭代器,就能够方便的对集合进行遍历了。

      5) 对同一个容器对象。能够同一时候进行多个遍历。由于遍历状态是保存在每个迭代器对象中的。

      6) 封装性良好,用户仅仅须要得到迭代器就能够遍历。而对于遍历算法则不用去关心。

      7) 在 Java Collection 中,迭代器提供一种高速失败机制 ( ArrayList是线程不安全的,在ArrayList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行改动,否则在其它线程中以不论什么形式对列表进行改动。迭代器立即会抛出异常。高速失败),防止多线程下迭代的不安全操作。


     由此。也能够得出迭代器模式的适用范围:

      1) 訪问一个容器对象的内容而无需暴露它的内部表示;

      2) 支持对容器对象的多种遍历;

      3) 为遍历不同的容器结构提供一个统一的接口(多态迭代)


    三、Iterator 迭代器与 Iterable 接口

    1、Iterator 迭代器接口 : java.util 包

      Java 提供一个专门的迭代器接口 Iterator,我们能够对某个容器实现该 Interface,来提供标准的 Java 迭代器。


    • 用 Iterator 模式实现遍历集合

        Iterator 模式是用于遍历集合类的标准訪问方法。它能够把訪问逻辑从不同类型的集合类中抽象出来。从而避免向client暴露集合的内部结构。

        比如,假设没有使用 Iterator。遍历一个数组 的方法是使用索引:

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

      而 遍历一个HashSet必须使用 while 循环或 foreach,但不能使用for循环:

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

      对以上两种方法。client都必须事先知道集合的类型(内部结构),訪问代码和集合本身是紧耦合的,无法将訪问逻辑从集合类和client代码中分离出来,从而导致每一种集合对应一种遍历方法,client代码无法复用。更恐怖的是。假设以后须要把 ArrayList 更换为 LinkedList。则原来的client代码必须全部重写。

      为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

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

      奥秘在于 client自身不维护遍历集合的”指针”。全部的内部状态(如当前元素位置,是否有下一个元素)都由 Iterator 来维护。而这个 Iterator 由集合类通过工厂方法生成,因此。它知道怎样遍历整个集合。并且。client从不直接和集合类打交道。它总是控制Iterator。向它发送”向前”,”向后”,”取当前元素”的指令。就能够间接遍历整个集合。

      首先看看 java.util.Iterator 接口的定义:

    public interface Iterator {
        boolean hasNext(); 
        Object next(); 
        void remove(); // 可选操作 
    } 

      依赖前两个方法就能完毕遍历,典型的代码例如以下:

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

      多态迭代 : 每一种集合类返回的 Iterator 详细类型可能不同,Array 可能返回 ArrayIterator。Set 可能返回 SetIterator,Tree 可能返回 TreeIterator,可是它们都实现了 Iterator 接口,因此,client不关心究竟是哪种 Iterator,它仅仅须要获得这个 Iterator 接口就可以,这就是面向对象的威力。


    2、Iterable 接口 : java.lang 包

      Java 中还提供了一个 Iterable 接口,Iterable接口实现后的功能是“返回”一个迭代器 。我们经常使用的实现了该接口的子接口有: Collection<E>系列,包括 List<E>, Queue<E>, Set<E> 在内。特别值得一提的是。Map 接口没有实现 Iterable 接口。该接口的 iterator() 方法返回一个标准的 Iterator 实现。


    • 实现 Iterable 接口来实现适用于 foreach 遍历的自己定义类

        Iterable 接口包括一个能够产生 Iterator 的 iterator() 方法。并且 Iterable 接口被 foreach 用来在序列中实现移动。因此,实现这个接口同意对象成为 foreach 语句的目标,也就能够通过 foreach语法遍历你的底层序列。

        在 JDK1.5 曾经,用 Iterator 遍历序列的语法:

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

      在 JDK1.5 以及以后的版本号中,引进了 foreach,对上面的代码在语法上作了简化 ( 可是限于仅仅读,假设须要remove。还是直接使用 Iterator )

    for(Type t : collection) { ... } 

    3、思辨

    • 为什么一定要去实现 Iterable 这个接口呢? 为什么不直接实现 Iterator接口 呢?

        看一下 JDK 中的集合类。比方 List一族或者Set一族。都是实现了 Iterable 接口,但并不直接实现 Iterator 接口。细致想一下这么做是有道理的:由于 Iterator接口的核心方法 next() 或者 hasNext() 是依赖于迭代器的当前迭代位置的。

      若 Collection 直接实现 Iterator 接口,势必导致集合对象中包括当前迭代位置的数据(指针)。当集合在不同方法间被传递时,由于当前迭代位置不可预置。那么 next() 方法的结果会变成不可预知。除非再为 Iterator接口 加入一个 reset() 方法。用来重置当前迭代位置。但即使这样,Collection 也仅仅能同一时候存在一个当前迭代位置(不能同一时候多次迭代同一个序列:必须要等到当前次迭代完毕并reset后,才干再一次从头迭代)。 而选择实现 Iterable 接口则不然,每次调用都会返回一个从头開始计数的迭代器(Iterator),因此,多个迭代器间是互不干扰的。


    四、foreach,Iterator。for

    • foreach 和 Iterator 的关系

        foreach 是 jdk5.0 新添加的一个循环结构,能够用来处理集合中的每个元素而不用考虑集合的下标。

    格式例如以下 :

     for(variable:collection){ statement; }

       定义一个变量用于暂存集合中的每个元素,并运行对应的语句(块)。

    Collection 必须是一个数组或者是一个实现了 lterable 接口的类对象。

       能够看出,使用 foreach 循环语句的优势在于更加简洁,更不easy出错,不必关心下标的起始值和终止值。forEach 不是关键字,关键字还是 for ,语句是由 iterator 实现的,它们最大的不同之处就在于 remove() 方法上。

       特别地。一般调用删除和加入方法都是详细集合的方法。比如:

    List list = new ArrayList(); 
    list.add(...); 
    list.remove(...);
    ...

      可是。假设在循环的过程中调用集合的 remove() 方法,就会导致循环出错,由于循环过程中 list.size() 的大小变化了,就导致了错误(Iterator的高速失败机制)。

    所以,假设想在循环语句中删除集合中的某个元素,就要用迭代器 iterator 的 remove() 方法,由于它的 remove() 方法不仅会删除元素,还会维护一个标志,用来记录眼下是不是可删除状态,比如,你不能连续两次调用它的remove()方法。调用之前至少有一次 next() 方法的调用。因此,foreach 就是为了让用 iterator 循环訪问的形式简单。写起来更方便。

    当然功能不太全,所以若是须要使用删除操作,那么还是要用它原来的形式。


    • 使用for循环与使用迭代器iterator的对照

      从效率角度分析:

        採用 ArrayList 对随机訪问比較快,而for循环中的get()方法。採用的即是随机訪问的方法,因此在ArrayList里,for循环较快。

        採用 LinkedList 则是顺序訪问比較快,iterator 中的next()方法,採用的即是顺序訪问的方法,因此在LinkedList里,使用iterator较快。

      从数据结构角度分析:

        使用 for循环 适合訪问有序结构。能够依据下标高速获取指定元素。而 Iterator 适合訪问无序结构,由于迭代器是通过 next() 和 Pre() 来定位的。能够訪问没有顺序的集合.

         使用 Iterator 的优点在于能够使用同样方式去遍历集合中元素,而不用考虑集合类的内部实现(仅仅要它实现了 java.lang.Iterable 接口)。假设使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做不论什么改动。假设使用 for 来遍历,那全部遍历此集合的算法都得做对应调整,由于List有序,Set无序,结构不同,他们的訪问算法也不一样.


    五、ListIterator 简述

    1、简述

       ListIterator 系列表迭代器,实现了Iterator<E>接口。

    该迭代器同意程序猿按任一方向遍历列表、迭代期间改动列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置,如以下的插入符举例说明:

              ListIterator.png-2kB

       注意。remove() 和 set(Object) 方法不是依据光标位置定义的;它们是依据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。


    2、与 Iterator 差别

       Iterator 和 ListIterator 主要差别有:

    • ListIterator 有 add()方法,能够向 List 中加入对象,而 Iterator 不能 ;

    • ListIterator 和 Iterator 都有 hasNext()和next()方法,能够实现顺序向后遍历。可是 ListIterator 有 hasPrevious() 和 previous() 方法,能够实现逆向(顺序向前)遍历,而 Iterator 就不能够 ;

    • ListIterator 能够利用 nextIndex() 和 previousIndex() 定位当前的索引位置,而 Iterator 没有此功能 ;

    • ListIterator 能够通过 listIterator() 方法和 listIterator(int index) 方法获得,而 Iterator 仅仅能由 iterator() 方法获得 ;

    • 二者都能够实现删除对象,可是ListIterator能够使用set()方法实现对象的改动。Iterator 仅能遍历,不能改动。由于ListIterator的这些功能。能够实现对LinkedList, ArrayList等List数据结构的操作。


    引用:

    JDK APK 1.6.0
    深入浅出Java设计模式之迭代器模式
    23种设计模式(13):迭代器模式
    Java程序猿从笨鸟到菜鸟之(四十五)大话设计模式(九)迭代器模式和命令模式
    Java迭代器深入理解及使用
    Java迭代器
    iterator与iterable
    java Iterable接口和Iterator迭代器
    Iterator和ListIterator

  • 相关阅读:
    cf B. Vasily the Bear and Fly
    hdu 3339 In Action
    hdu 六度分离
    cf A. Vasily the Bear and Triangle
    cf C. Secrets
    2.19学习笔记|2.20学习笔记
    VAE代码学习
    2.9日学习记录
    deconvolution反卷积(待学习)
    gamma分布学习
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7399094.html
Copyright © 2020-2023  润新知