• 【深入设计模式】迭代器模式模式—什么是迭代器模式?


    当我们在排队买车票时,售票员需要一个一个的对买票的人身份进行核实并收款出票,这个时候乘客就是一个集体,而售票员需要依次对这个集体里面的每个人处理业务,这个售票员依次处理乘客业务的过程就是一个迭代器模式的体现。

    1. 迭代器模式

    1.1 迭代器模式简介

    迭代器模式比较简单,就是提供了依次访问聚集对象中每个元素的方法,但是并不会对外暴露这个聚集对象内部的实现。从这里的可以看出其实迭代器模式就是提供了一种遍历集合的机制,而具体如何遍历便可以通过接口层面的实现来完成具体的遍历逻辑,这样对外也就仅仅提供统一的方法进行遍历,调用者也不需要知道具体的实现。

    1.2 迭代器模式结构

    迭代器模式通常由以下几个角色组成:

    • 迭代器(Iterator):定义该迭代器对外提供的方法
    • 具体迭代器(ConcreteIterator):具体迭代器,使用迭代器中定义的方法
    • 抽象集合(Collection):被遍历的对象定义
    • 具体集合(ConcreteCollection):被遍历对象的具体实现

    我们用代码将上面的结构给表示出来,由于需要实际执行,那么在 Collection 类中就需要定义添加元素、获取元素和当前 Collection 大小三个方法(简化操作像删除等其他方法就省略)。代码如下:

    // 迭代器
    public interface Iterator {
        boolean hasNext();
    
        Object next();
    }
    // 具体迭代器
    public class ConcreteIterator implements Iterator {
        private Collection collection;
        int index = 0;
    
        ConcreteIterator(Collection collection) {
            this.collection = collection;
        }
    
        @Override
        public boolean hasNext() {
            return index != collection.size();
        }
    
        @Override
        public Object next() {
            return collection.get(index++);
        }
    }
    // 抽象集合
    public abstract class Collection {
    
        public abstract int size();
    
        public abstract Object get(int index);
    
        public abstract void add(Object ele);
    
        public abstract Iterator iterator();
    }
    // 具体集合,其中增加添加、获取元素和集合大小三个方法
    public class ConcreteCollection extends Collection {
        Object[] elements;
        private static final int COLLECTION_SIZE = 1 << 10;
        int index;
    
        ConcreteCollection() {
            index = 0;
            elements = new Object[COLLECTION_SIZE];
        }
    
        @Override
        public int size() {
            return index;
        }
    
        @Override
        public Object get(int index) {
            return elements[index];
        }
    
        @Override
        public void add(Object ele) {
            elements[index++] = ele;
        }
    
        @Override
        public Iterator iterator() {
            return new ConcreteIterator(this);
        }
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    接下来对上面这段代码进行执行,首先通过 add() 方法往 Collection 对象中添加元素,分别是 a->1->b->false 四个元素。然后从 Collection 对象中获取到迭代器对象,然后通过迭代器来对集合进行遍历,代码如下:

    public static void main(String[] args) {
        Collection collection = new ConcreteCollection();
        collection.add("a");
        collection.add(1);
        collection.add("b");
        collection.add(false);
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    控制台输出结果如下,可以看到通过迭代器遍历集合最终按照放入的顺序依次读取出来:

    a
    1
    b
    false
    
    • 1
    • 2
    • 3
    • 4

    1.3 迭代器模式示例

    接下来用个示例来对迭代器进行讲解加深印象。当出现疫情的时候需要大家积极去做核酸检测,做检测的流程都是扫码、消毒、采样,检测人员需要对来排队做检测的人一个一个完成这几步骤。加入张三、李四、王五三个人正在排队,那么我们可以用迭代器模式来完成这三个人的核酸检测流程。

    套用上面示例的结构,我们可以直接沿用上面的迭代器和集合类,我们将张三、李四、王五放入集合中,即开始排队做核酸,然后通过Collection 的迭代器便能够依次对这三个人完成检测。整体代码和前面是一致的,只是在获取元素之后的处理流程有所不同。

    public static void main(String[] args) {
        System.out.println("===排队做核酸===");
        Collection collection = new ConcreteCollection();
        collection.add("张三");
        collection.add("李四");
        collection.add("王五");
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            String name = iterator.next().toString();
            System.out.println(name+" 扫码");
            System.out.println(name+" 消毒");
            System.out.println(name+" 检测");
            System.out.println("===完成采样!下一个===");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    同样的沿用前面的代码,我们只需从 Collection 对象中取出 Iterator 对象便能够完成对集合内元素的遍历,控制台展示如下:

    ===排队做核酸===
    张三 扫码
    张三 消毒
    张三 检测
    ===完成采样!下一个===
    李四 扫码
    李四 消毒
    李四 检测
    ===完成采样!下一个===
    王五 扫码
    王五 消毒
    王五 检测
    ===完成采样!下一个===
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    看了前面的结构代码和示例,在这里做一点小总结。首先我们通过 Iterator 对象来遍历集合,那么在代码中可以看到有一个 index 属性来指定当前遍历的位置,但是代码中并没有给出该属性 index 的设置,也就是说使用 Iterator 对象遍历集合的时候,当我们遍历到一个位置 index 后,后续也只能从 index 开始遍历,不会再从头开始遍历。如果需要再次从头遍历,则需要拿出一个新的 Iterator 对象才行了。

    2. 迭代器模式在框架源码中的应用

    迭代器在源码中的应用当然就要说一说 JDK 里面的集合框架了,以 ArraysList 为例,在 ArrayList 类中提供了 iterator 方法来获取迭代器,从下面代码可以看出 iterator() 方法内部返回的是一个 Itr 对象。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    	...省略
        /**
         * Returns an iterator over the elements in this list in proper sequence.
         *
         * 

    The returned iterator is fail-fast. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); } ...省略 }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Itr 类是 ArrayList 类中的内部类,该类实现 Iterator 接口,从而 ArraysList 通过 Itr 对 ArrayList 提供定制化功能的迭代器。从下面代码中可以看出,在 Itr 内部的 hasNext 和 next 两个方法都会用到外部类 ArrayList 的 size 属性,通过 Itr 的 cursor 和 size 来判断当前迭代器所处在集合的位置。

    /**
    * An optimized version of AbstractList.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();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
    
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    3. 总结

    迭代器模式是比较简单的设计模式,其定义了遍历一个集合提供了统一的接口,其广泛应用于结合框架中。在迭代器中,需要一个属性来指示当前迭代器遍历到结合的位置,并且这个属性只能向后遍历,不能够再从头开始(当然通过定制化开发能够实现),因此迭代器模式也叫游标模式。在前面的两个示例代码中,我们可以看到在使用迭代器遍历集合的时候我们并不需要知道集合中到底是用的什么样的数据结构,仅仅通过 next() 和 hasNext() 两个方法便能够完成遍历,所以我们使用迭代器模式能够隐藏集合的内部结构和实现,并且在需要对遍历进行定制化开发的时候只需要实现 Iterator 接口重写这两个方法即可。

    最近被疫情又来了,小区和地区都封闭已经在家办公快一星期,真希望这疫情早点结束,大家都一起加油,争取早日消灭疫情,共度难关!

  • 相关阅读:
    01|JVM类加载机制
    基于SpringBoot的仿京东商城系统
    Servlet urlPatterns配置
    芯邦'CBM2099E
    元宇宙011 | 元宇宙的沉浸式体验会成瘾吗?
    CAS-比较并交换
    PMP 11.27 考试倒计时13天!冲刺啦!
    Vim基础用法,最常用、最实用的命令介绍(保姆级教程)
    研究生怎么申请专利,流程是什么?
    SpringBoot 概念、优点以及网页版创建
  • 原文地址:https://blog.csdn.net/qq_38550836/article/details/126109655