设计模式之迭代器模式

文章目录

  • 一、介绍
  • 二、迭代器模式中的角色
  • 三、代码演示
    • [1. 支持迭代的集合抽象接口(`BarIterable`)](#1. 支持迭代的集合抽象接口(BarIterable))
    • [2. 具体的集合类(`BarList`)](#2. 具体的集合类(BarList))
    • [3. 迭代器抽象接口(`FooIterator`)](#3. 迭代器抽象接口(FooIterator))
    • [4. 迭代器具体实现类(`FooItr`)](#4. 迭代器具体实现类(FooItr))
    • [5. 代码测试](#5. 代码测试)
  • 四、java中迭代器模式的应用
    • [1. `Iterable`接口](#1. Iterable接口)
    • [2. 迭代器抽象接口`Iterator`](#2. 迭代器抽象接口Iterator)
    • [3. `ArrayList`集合对迭代器模式的实现](#3. ArrayList集合对迭代器模式的实现)
    • [4. `LinkedList`集合对迭代器模式的实现](#4. LinkedList集合对迭代器模式的实现)
    • [5. 迭代器与增强for循环](#5. 迭代器与增强for循环)
  • 五、优缺点

一、介绍

迭代器模式(Iterator Pattern) ,属于行为型设计模式,在javapython中十分常见。目的是在不暴露集合内部结构的条件下,顺序访问该集合内部的元素。

提供一种顺序访问一个集合中所有元素的方式, 而又无需暴露该对象的内部表示。

在需要顺序访问集合元素的场景中,传统方式是通过以下方式对集合元素进行遍历

java 复制代码
for(int i=0; i<list.size(); i++) {
    Object obj = list.get(i);
}

在该方式中,对元素的获取是通过直接调用集合的get()方法完成的,即对元素的遍历由集合本身负责。

而使用迭代器设计模式后,集合本身不负责元素的遍历,而提供一个获取迭代器的方法iterator(),对元素的遍历由迭代器负责,如下所示

java 复制代码
Iterator<Object> iterator = list.iterator();
while(iterator.hasNext()) {
    Object obj = iterator.next();
}

这是一种对责任细分的体现,集合将对元素遍历的责任交给迭代器完成。

迭代器的使用也十分简单,一般来讲,只提供两个方法:hasNext()next()hasNext()方法用于遍历集合,next()方法用于顺序获取集合中的元素。

二、迭代器模式中的角色

在迭代器模式中,所有类型的集合(无论底层是数组还是链表),都需要提供一个获取迭代器的方法,因此我们可以将该方法抽象出来封装到一个独立的**抽象接口Iterable**中,实现该接口的任何集合都具备获取迭代器的能力。

迭代器无论采取什么样的遍历方式,都需要两个基本方法hasNext()next(),因此我们将这两个方法抽象到接口类Iterator中。

因此进过分析,可以确定在迭代器模式中,存在四个基本角色:支持迭代的接口类具体集合类迭代器抽象接口类具体迭代器类

  • 支持迭代的集合抽象接口(BarIterable)

    该接口定义一个获取迭代器的方法iterator(),实现该接口的所有集合类都需要实现对应的逻辑。

    另外,在声明该抽象接口时,还需要在接口上声明一个泛型<T>,因为这是一个集合接口,意味着集合中的元素可以是任意类型。同理,iterator()方法返回的迭代器对象也应该标注泛型<T>

  • 具体的集合类(BarList)

    实现抽象接口(BarIterable) ,按照本身的实际情况对iterator()方法进行实现。

    与上面接口类BarIterable类似,该集合类和其实现的iterator()方法也应该各声明一个泛型<T>

  • 迭代器抽象接口(FooIterator)

    该接口定义了迭代器的基本行为,遍历和获取,分别用hasNext()方法和next()方法表示。

    作为迭代器,也应该在类上声明一个泛型<T>,原因同上。因此其next()方法的返回值也应该是泛型<T>

  • 迭代器具体实现类(FooItr)

    实现迭代器抽象接口FooIterator

    由于迭代器的功能是对集合中的元素进行遍历,因此我们常用的做法是将具体迭代器声明为集合类的内部类,这样迭代器就可以直接访问集合中的元素了。

因此迭代器模式的通用UML图如下所示

三、代码演示

根据以上对迭代器模式中各个角色的分析,我们使用代码对其进行演示

1. 支持迭代的集合抽象接口(BarIterable)

新建接口类BarIterable,并定义iterator()方法,同时声明接口类的泛型<T>

java 复制代码
public interface BarIterable<T> {

    /** 获取迭代器 **/
    FooIterator<T> iterator();

}

2. 具体的集合类(BarList)

新建集合类BarList,实现接口BarIterable

java 复制代码
public class BarList<T> implements BarIterable<T> {

    private final Object[] array = new Object[]{1,2,3,4,5,6,7,8,9};

    @Override
    public FooIterator<T> iterator() {
        return new FooItr();
    }
}

3. 迭代器抽象接口(FooIterator)

新建迭代器接口类FooIterator,并定义hasNext()方法和next()方法,同时声明泛型<T>

java 复制代码
public interface FooIterator<T> {

    boolean hasNext();

    T next();
}

4. 迭代器具体实现类(FooItr)

新建迭代器实现类FooItr,实现接口类FooIterator

由于迭代器的功能是对集合中的元素进行遍历,因此我们常用的做法是将具体迭代器声明为集合类的内部类,这样迭代器就可以直接访问集合中的元素了。

下面我们在集合类BarList中定义该内部类

java 复制代码
public class BarList<T> implements BarIterable<T> {

    private final Object[] array = new Object[]{1,2,3,4,5,6,7,8,9};

    @Override
    public FooIterator<T> iterator() {
        return new FooItr();
    }

    private class FooItr implements FooIterator<T> {

        private Integer index = 0;

        @Override
        public boolean hasNext() {
            return index < array.length;
        }

        @Override
        public T next() {
            return (T) array[index++];
        }
    }
}

在该内部类中,我们通过一个属性index

  • index属性

    用来访问集合中的数组结构,作为数组下标从数组中读取数据。

  • hasNext()方法

    数组中长度是固定的,根据当前index判断是否已经到达最后一个元素

  • next()方法

    直接通过index作为数组下标,从数组中读取数据。

5. 代码测试

新建一个测试类IteratorDemo,在main()方法中测试代码

java 复制代码
public class IteratorDemo {

    public static void main(String[] args) {

        BarList<Integer> barList = new BarList<>();
        
        FooIterator<Integer> iterator = barList.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            System.out.print(integer + " ");
        }
    }
}

运行后输出如下所示

四、java中迭代器模式的应用

在java的集合框架中,我们都知道所有的集合类都实现了Collection接口,但Collection接口并不是集合的顶级接口Iterable接口才是,也就是说,所有的集合类都实现了迭代器模式。下面是java集合框架的类图

我们以ArrayListLinkedList为例,按照上面我们对迭代器模式各个角色的分析,来看一下java集合是如何应用迭代器模式的

1. Iterable接口

Iterable接口如下所示,它定义了一个获取迭代器的方法iterator()

java 复制代码
public interface Iterable<T> {
    /** 获取迭代器 */
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

2. 迭代器抽象接口Iterator

java 复制代码
public interface Iterator<E> {
    /** 是否到达集合中最后一个元素 */
    boolean hasNext();

    /** 获取集合中下一个元素 */
    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

3. ArrayList集合对迭代器模式的实现

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    transient Object[] elementData;
    
    // 省略无关代码
    
    // 获取迭代器
    public Iterator<E> iterator() {
        return new Itr();
    }
    
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            // 数组下标+1
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            // ...
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            // ...
        }

        final void checkForComodification() {
            // ...
        }
    }
}

4. LinkedList集合对迭代器模式的实现

java 复制代码
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient Node<E> first;
    transient Node<E> last;
    
    // 获取迭代器
    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

    // ListIterator接口继承于Iterator接口,对Iterator接口定义的方法进行补充
    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;
        
        // 省略无关代码

        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            // next.next获取下一个元素
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }
    }
}

5. 迭代器与增强for循环

我们遍历一个集合时,最常使用的方式就是增强for循环 了,即for(T t : 集合),那么它和迭代器有什么关系呢?

我们通过增强for循环演示一段代码

java 复制代码
public class Demo {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f");

        for (String s : list) {
            System.out.print(s + " ");
        }
    }
}

输出结果为:

下面我们看一下java对该类所编译出的class文件

从该class文件中我们可以看到,我们在java文件中编写的增强for循环 在编译过程中被转换成了迭代器

五、优缺点

优点:

  • 简化了集合类。集合类中遍历元素的责任转移给迭代器了。
  • 在一个集合中可以存在多种遍历方式,这取决于你创建了多少个内部的迭代器实现类。

缺点:

  • 一种遍历方式对应一个迭代器类,这将增加系统中类的数量

纸上得来终觉浅,绝知此事要躬行。

------------------------我是万万岁,我们下期再见------------------------

相关推荐
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ7 分钟前
idea 弹窗 delete remote branch origin/develop-deploy
java·elasticsearch·intellij-idea
Code成立9 分钟前
《Java核心技术 卷I》用户图形界面鼠标事件
java·开发语言·计算机外设
鸽鸽程序猿34 分钟前
【算法】【优选算法】二分查找算法(下)
java·算法·二分查找算法
遇见你真好。1 小时前
自定义注解进行数据脱敏
java·springboot
NMBG221 小时前
[JAVAEE] 面试题(四) - 多线程下使用ArrayList涉及到的线程安全问题及解决
java·开发语言·面试·java-ee·intellij-idea
像污秽一样1 小时前
Spring MVC初探
java·spring·mvc
计算机-秋大田1 小时前
基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
LuckyLay1 小时前
Spring学习笔记_36——@RequestMapping
java·spring boot·笔记·spring·mapping
醉颜凉2 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
阿维的博客日记2 小时前
java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程
java·jvm