• 迭代器


    在我的工作中,广泛使用了 List 类型的引用变量,而引用的是具体的类 ArrayList。我们通常会使用 for 循环遍历一个 List,每一次循环为 List 调用的 get 方法传入一个循环变量,这样就取到了具体位置的元素,并进行业务上的处理。那么,我们为什么需要 Iterator 接口?

        通过 for 循环遍历的方式,需要事先知道其内部结构,每种集合有各自的遍历方法,由此产生的是访问代码和集合本身形成了紧耦合,不利于复用。想象一下,当初使用了 ArrayList 处理数据,而现在告诉你要使用 LinkedList,而你的业务代码又复杂而繁琐……好了,现在有了Iterator接口,你再也不用关心你遍历的集合的具体实现,统一通过调用 Iterator 的方法,使用同一种逻辑就可以间接遍历整个集合,何乐而不为呢!

    一、迭代器模式

        迭代器模式,它提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。具体的几点要求:

    1、访问一个聚合对象的内容而无需暴露它的内部表示;

    2、支持对聚合对象的多种遍历;

    3、为遍历不同的聚合结构提供一个统一的接口。

        Iterator 接口正是做到了这几点,下面,看看 Iterator 接口定义的三个方法。

    二、Iterator 接口方法

        1、boolean hasNext()

                文档:Returns true if the iteration has more elements. (In other words, returns true if next would return an element rather than throwing an exception.)

                常用在 next 方法之前,用于判断 next 方法是否能返回一个元素,不至于调用 next 方法是抛出异常。

        2、E next()

                文档:Returns the next element in the iteration.

                文档很容易理解,注意这里使用了泛型就可以了。

        3、void remove()

                文档:Removes from the underlying collection the last element returned by the iterator (optional operation). This method can be called only once per call to next. The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.

                remove 方法将删除最后一次使用 next 方法返回的元素,需要注意每次调用了 next 方法后,此方法只能被调用一次,否则抛出 IllegalStateException 异常。

                如果进行迭代时用调用此方法之外的其他方式修改了该迭代器所指向的collection,则迭代器的行为是不确定的。也就是说,在迭代时使用 remove 方法修改了迭代器指向的 collection,迭代器的行为能确定,但调用其它方法却不是这样的。

    三、ListIterator 接口

        ListIterator 继承了 Iterator。ListIterator 是系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素,它的光标位置始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置(第一个元素之前和最后一个元素之后)。

        下面逐一介绍 ListIterator 接口的方法:

        1、void add(E e)

            该方法可以将指定的对象元素 e 加入到列表。需要特别留意元素被加入列表的位置:

                (1)next 方法返回的下一个元素之前;

                (2)previous 方法返回的下一个元素之后;

                (3)列表为空时,成为列表唯一的元素。

        2、boolean hasNext()

            常用在 next 方法之前,用于判断 next 方法是否能返回一个元素,不至于调用 next 方法是抛出异常。

        3、boolean hasPrevious()

            逆向遍历列表方式下,用在 previous 方法之前,判断 previous 方法是否能返回一个元素,而不是抛出异常。

        4、E next()

            返回列表中的下一个元素。

        5、int nextIndex()

            返回对 next 后续调用所返回元素的索引。

            请看例子:

    // 声明一个List
    List<String> list = new ArrayList<String>();
    
    // 添加元素
    list.add("1");
    list.add("2");
    list.add("3");
    
    // 获取迭代器
    ListIterator<String> li = list.listIterator();
    
    // 迭代遍历
    while(li.hasNext()) {
        System.out.println(li.next() + ": " + li.nextIndex());
        li.remove();
    }
    结果如下:
    1: 1
    2: 1
    3: 1

            可见 nextIndex 返回的是(next 所返回元素的索引 + 1)。如果当前 next 返回的元素是列表最后一个元素,那么 nextIndex 返回的值就是整个列表的大小。

        6、E previous()

            返回列表中的上一个元素。

        7、int previousIndex()

            返回对 next 后续调用所返回元素的索引。

            请看例子:

    // 声明List
    List<String> list = new ArrayList<String>();
    
    // 添加元素	
    list.add("1");
    list.add("2");
    list.add("3");
    
    // 获取迭代器
    ListIterator<String> li = list.listIterator();
    
    // 正向遍历
    while(li.hasNext()) {
        System.out.println(li.next() + ": " + li.nextIndex());
    }
    
    // 逆向遍历
    while(li.hasPrevious()) {
        System.out.println(li.previous() + ": " + li.previousIndex());
    }

            

    结果如下:
    1: 1
    2: 2
    3: 3
    3: 1
    2: 0
    1: -1

            正向遍历是为了将光标移至列表尾部。可见,在逆向遍历方式中,previousIndex 返回的是(previous 所返回元素的索引 - 1)。如果当前 previous 返回的是列表中第一个元素,那么 previousIndex 返回的值等于-1。

        8、void remove()

            从列表中删除 next 或 previous 返回的最后一个元素。每一次调用 next 或 previous 方法之后只能调用一次此方法,另外,如果有 add 方法调用,那么 remove 方法必须在 add 方法之前被执行。

        9、void set(E e)

            set 方法提供的其实是一个修改功能,它将指定的元素 e 替换 next 或 previous 返回的最后一个元素。

    // 声明List
    List<String> list = new ArrayList<String>();
    
    // 添加元素
    list.add("1");
    list.add("2");
    list.add("3");
    
    // 获取迭代器
    ListIterator<String> li = list.listIterator();
    
    // 正向遍历并修改元素
    System.out.println("修改前:");
    while(li.hasNext()) {
        System.out.println(li.next());
        li.set("4");
    }
    
    // 逆向遍历并输出元素
    System.out.println("修改后:");
    while(li.hasPrevious()) {
        System.out.println(li.previous());
    }
    结果
    修改前:
    1
    2
    3
    修改后:
    4
    4
    4

    四、Iterable 接口

        Iterator接口中定义的三个方法都依赖于迭代器的当前迭代位置,如果让这些集合直接实现 Iterator 接口,那么这些集合接口和类中势必需要定义一些储存当前迭代位置的成员变量。由此而产生的问题是,当在多处使用一个集合时,由于迭代位置的不确定性,调用该集合的 next 方法时返回的结果也是不可预知的。

        那么有什么办法呢?Iterable接口为我们提供了一个 iterator 方法,每当调用该方法时,便会返回一个新的迭代器,而这些迭代器是互不干扰的,这样便可以使用多个迭代器的方式来处理数据。如果希望更深一步研究Iterator和Iterable,可以去研究研究JDK中是如何实现了Iterable接口的。

        在CSDN论坛中一个关于两者区别的回答:

    Iterable接口实现后的功能是“返回”一个迭代器,而Iterator接口实现后的功能是“使用”一个迭代器。

        本文的例子中,用到了 iterator 方法,因为ArrayList 实现了Iterable接口,调用 iterator 方法返回一个迭代器,在对迭代器进行操作即可。

        下面看看API中如何说明 iterator 方法:

            Iterator<T> iterator() —— Returns an iterator over a set of elements of type T.

  • 相关阅读:
    Rust语言环境配置;Rust版本1.45.2学习
    《代码英雄》系列整理
    Pingcap High Performance TiDB 课程学习记录@yaowenxu
    11. C语言百钱买百鸡问题(详解版)
    10. C语言输出菱形(详解版)
    9. C语言判断素数(求素数)(两种方法)
    8. C语言求圆周率π(三种方法)
    7. C语言杨辉三角(两种方法)
    6. C语言九九乘法表(五种输出形式)
    5. C语言希尔排序算法
  • 原文地址:https://www.cnblogs.com/bdqczhl/p/7064204.html
Copyright © 2020-2023  润新知