• 设计模式之迭代器模式


    一、介绍

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

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

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

    for(int i=0; i<list.size(); i++) {
        Object obj = list.get(i);
    }
    
    • 1
    • 2
    • 3

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

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

    Iterator<Object> iterator = list.iterator();
    while(iterator.hasNext()) {
        Object obj = iterator.next();
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

    二、迭代器模式中的角色

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

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

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

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

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

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

    • 具体的集合类(BarList)

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

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

    • 迭代器抽象接口(FooIterator)

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

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

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

      实现迭代器抽象接口FooIterator

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

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

    在这里插入图片描述

    三、代码演示

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

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

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

    public interface BarIterable<T> {
    
        /** 获取迭代器 **/
        FooIterator<T> iterator();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 具体的集合类(BarList)

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

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    public interface FooIterator<T> {
    
        boolean hasNext();
    
        T next();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

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

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

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

    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++];
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    • index属性

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

    • hasNext()方法

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

    • next()方法

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

    5. 代码测试

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

    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 + " ");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行后输出如下所示

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    1. Iterable接口

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

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 迭代器抽象接口Iterator

    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());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    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() {
                // ...
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

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

    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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    5. 迭代器与增强for循环

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

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

    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 + " ");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    输出结果为:

    在这里插入图片描述

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

    在这里插入图片描述

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

    五、优缺点

    优点:

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

    缺点:

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


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

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

  • 相关阅读:
    六、TCP实现聊天
    使用 Docker 部署 VS Code in The Browser
    HOJ排队打水F601题解
    Ubuntu下载odbc驱动
    【Java面试】List接口
    Vue城市选择器示例(省市区三级)
    Qt Designer基础控件介绍
    1.【算法五章】
    总结710
    MySQL常用函数
  • 原文地址:https://blog.csdn.net/qq_36234720/article/details/134039381