前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >迭代器模式--沙场秋点兵

迭代器模式--沙场秋点兵

作者头像
zhanyd
发布2022-05-16 13:57:28
2670
发布2022-05-16 13:57:28
举报
文章被收录于专栏:编程我也会

引子

小帅在军中官至军师,身居高位,必然要尽心尽责,最近又要主动进行士兵普查,遂命各副将按各个兵种准备士兵名册。

统领步兵的副将周仓,带队骑兵的副将马良,各自领命回去准备。

没过几日,周仓和马良就把各兵种的名册呈上来了,小帅翻开一看......

士兵类:

代码语言:javascript
复制
/**
* 士兵类
*/
public class Soldier {
  /**
   * 姓名
   */
  String name;
  /**
   * 兵种
   */
  String unit;
  /**
   * 所属
   */
  String belongs;

  public Soldier(String name, String unit, String belongs) {
      this.name = name;
      this.unit = unit;
      this.belongs = belongs;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public String getUnit() {
      return unit;
  }

  public void setUnit(String unit) {
      this.unit = unit;
  }

  public String getBelongs() {
      return belongs;
  }

  public void setBelongs(String belongs) {
      this.belongs = belongs;
  }

  @Override
  public String toString() {
      return "姓名:" + name + ", 兵种:" + unit + ", 所属:" + belongs;
  }
}

周仓队的步兵名册是用数组保存的:

代码语言:javascript
复制
/**
 * 周仓队
 */
public class ZhouCangArrayArmy {

    static final int MAX_ITEMS_NUM = 5;
    Soldier[] soldierArray;
    int index = 0;

    public ZhouCangArrayArmy() {
        soldierArray = new Soldier[MAX_ITEMS_NUM];
        addItem("华季鸣","步兵","周仓队");
        addItem("春孟心","步兵","周仓队");
        addItem("务孟晓","步兵","周仓队");
        addItem("成仲爰","步兵","周仓队");
        addItem("汉孟宝","步兵","周仓队");
    }

    /**
     * 添加元素到数组
     * @param name
     * @param unit
     * @param belongs
     */
    public void addItem(String name, String unit, String belongs) {
        Soldier soldier = new Soldier(name, unit, belongs);
        if(index >= MAX_ITEMS_NUM) {
            System.out.println("数组已满,无法添加。");
        } else {
            soldierArray[index] = soldier;
            index++;
        }
    }

    /**
     * 获取士兵数组
     * @return
     */
    public Soldier[] getSoldiers() {
        return soldierArray;
    }

}

马良队的骑兵名册是用List保存的:

代码语言:javascript
复制
/**
 * 马良队
 */
public class MaliangListArmy {

    ArrayList<Soldier> soldierList;

    public MaliangListArmy() {
        soldierList = new ArrayList<Soldier>();
        addItem("达海青","轻骑兵","马良队");
        addItem("严行秋","轻骑兵","马良队");
        addItem("卓重云","轻骑兵","马良队");
        addItem("王勇申","轻骑兵","马良队");
        addItem("邱协洽","轻骑兵","马良队");
    }

    /**
     * 添加元素到列表
     * @param name
     * @param unit
     * @param belongs
     */
    public void addItem(String name, String unit, String belongs) {
        Soldier soldier = new Soldier(name, unit, belongs);
        soldierList.add(soldier);
    }

    /**
     * 获取士兵列表
     * @return
     */
    public ArrayList<Soldier> getSoldiers() {
        return soldierList;
    }

}

小帅开始遍历名册:

代码语言:javascript
复制
/**
 * 遍历士兵
 */
public class InspectSoldierNormal {
    public static void main(String[] args) {
        // 遍历周仓队
        ZhouCangArrayArmy zhouCangArrayArmy = new ZhouCangArrayArmy();
        Soldier[] soldierArray = zhouCangArrayArmy.getSoldiers();
        for(int i = 0; i < soldierArray.length; i++) {
            System.out.println(soldierArray[i]);
        }
      
        // 遍历马良队
        MaliangListArmy maliangListArmy = new MaliangListArmy();
        ArrayList<Soldier> soldierList = maliangListArmy.getSoldiers();
        for(int i = 0; i < soldierList.size(); i++) {
            System.out.println(soldierList.get(i));
        }
    }
}

遍历结果:

代码语言:javascript
复制
姓名:华季鸣, 兵种:步兵, 所属:周仓队
姓名:春孟心, 兵种:步兵, 所属:周仓队
姓名:务孟晓, 兵种:步兵, 所属:周仓队
姓名:成仲爰, 兵种:步兵, 所属:周仓队
姓名:汉孟宝, 兵种:步兵, 所属:周仓队
姓名:达海青, 兵种:轻骑兵, 所属:马良队
姓名:严行秋, 兵种:轻骑兵, 所属:马良队
姓名:卓重云, 兵种:轻骑兵, 所属:马良队
姓名:王勇申, 兵种:轻骑兵, 所属:马良队
姓名:邱协洽, 兵种:轻骑兵, 所属:马良队

小帅立马发现了问题,责问到:怎么你们的士兵名单存储的方式都不一样啊?两个人都用不同的存储结构,遍历的方式都不一样,这样查起来太麻烦啦!

二人面露难色,马良上前说道:是我们疏忽大意,没有事先统一好,不过现在名册都已经做好,恐不好变更了。

迭代器模式

小帅沉思片刻,对他们说道,你们看,虽然你们用的遍历方法不一样,但是也有共同点:

  1. 判断集合中还有没有元素
  2. 取出元素

这样,我们可以抽象出一个迭代器来,下面我就来介绍下迭代器模式吧。

迭代器模式(Iterator Design Pattern):提供一种方法顺序访问一个集合对象中的各个元素,而又不需暴露该对象的内部表示。

迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

迭代器模式也叫做游标模式(Cursor Design Pattern)。

用了迭代器模式之后的代码:

迭代器接口:

代码语言:javascript
复制
public interface Iterator<E> {
    boolean hasNext();
    E next();
}

周仓队迭代器实现类:

代码语言:javascript
复制
/**
 * 周仓队迭代器实现类
 */
public class ZhouCangArmyIterator implements Iterator {

    private int index = 0;
    private Soldier[] soldierArray;

    public ZhouCangArmyIterator(Soldier[] soldierArray) {
        this.soldierArray = soldierArray;
    }

    @Override
    public boolean hasNext() {
        if(index < soldierArray.length) {
            return true;
        }
        return false;
    }

    @Override
    public Soldier next() {
        Soldier soldier = soldierArray[index];
        index++;
        return soldier;
    }
}

马良队迭代器实现类:

代码语言:javascript
复制
/**
 * 马良队迭代器实现类
 */
public class MaliangArmyIterator implements Iterator {

    private int index = 0;
    private ArrayList<Soldier> soldierList;

    public MaliangArmyIterator(ArrayList<Soldier> soldierList) {
        this.soldierList = soldierList;
    }

    @Override
    public boolean hasNext() {
        if(index < soldierList.size()) {
            return true;
        }
        return false;
    }

    @Override
    public Soldier next() {
        Soldier item = soldierList.get(index);
        index++;
        return item;
    }

}

集合接口:

代码语言:javascript
复制
public interface ArmyCollection {
    Iterator createIterator();
}

周仓队实现类:

代码语言:javascript
复制
/**
 * 周仓队
 */
public class ZhouCangArrayArmy implements ArmyCollection{

    static final int MAX_ITEMS_NUM = 5;
    Soldier[] soldierArray;
    int index = 0;

    public ZhouCangArrayArmy() {
        soldierArray = new Soldier[MAX_ITEMS_NUM];
        addItem("华季鸣","步兵","周仓队");
        addItem("春孟心","步兵","周仓队");
        addItem("务孟晓","步兵","周仓队");
        addItem("成仲爰","步兵","周仓队");
        addItem("汉孟宝","步兵","周仓队");
    }

    /**
     * 添加元素到数组
     * @param name
     * @param unit
     * @param belongs
     */
    public void addItem(String name, String unit, String belongs) {
        Soldier soldier = new Soldier(name, unit, belongs);
        if(index >= MAX_ITEMS_NUM) {
            System.out.println("数组已满,无法添加。");
        } else {
            soldierArray[index] = soldier;
            index++;
        }
    }

    /**
     * 获取士兵数组
     * @return
     */
    public Soldier[] getSoldiers() {
        return soldierArray;
    }

    @Override
    public Iterator createIterator(){
        return new ZhouCangArmyIterator(soldierArray);
    }

}

马良队实现类:

代码语言:javascript
复制
/**
 * 马良队
 */
public class MaliangListArmy implements ArmyCollection{

    ArrayList<Soldier> soldierList;

    public MaliangListArmy() {
        soldierList = new ArrayList<Soldier>();
        addItem("达海青","轻骑兵","马良队");
        addItem("严行秋","轻骑兵","马良队");
        addItem("卓重云","轻骑兵","马良队");
        addItem("王勇申","轻骑兵","马良队");
        addItem("邱协洽","轻骑兵","马良队");
    }

    /**
     * 添加元素到列表
     * @param name
     * @param unit
     * @param belongs
     */
    public void addItem(String name, String unit, String belongs) {
        Soldier soldier = new Soldier(name, unit, belongs);
        soldierList.add(soldier);
    }

    /**
     * 获取士兵列表
     * @return
     */
    public ArrayList<Soldier> getSoldiers() {
        return soldierList;
    }

    @Override
    public Iterator createIterator() {
        return new MaliangArmyIterator(soldierList);
    }

}

遍历士兵:

代码语言:javascript
复制
/**
 * 遍历士兵
 */
public class InspectSoldierIterator {

    public static void main(String[] args) {
        // 遍历周仓队
        ZhouCangArrayArmy zhouCangArrayArmy = new ZhouCangArrayArmy();
        Iterator iterator = zhouCangArrayArmy.createIterator();
        // 调用统一的遍历方法
        traversalItems(iterator);

        // 遍历马良队
        MaliangListArmy maliangListArmy = new MaliangListArmy();
        iterator = maliangListArmy.createIterator();
        // 调用统一的遍历方法
        traversalItems(iterator);
    }

    /**
     * 遍历元素
     * @param iterator
     */
    public static void traversalItems(Iterator iterator) {
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

输出结果:

代码语言:javascript
复制
姓名:华季鸣, 兵种:步兵, 所属:周仓队
姓名:春孟心, 兵种:步兵, 所属:周仓队
姓名:务孟晓, 兵种:步兵, 所属:周仓队
姓名:成仲爰, 兵种:步兵, 所属:周仓队
姓名:汉孟宝, 兵种:步兵, 所属:周仓队
姓名:达海青, 兵种:轻骑兵, 所属:马良队
姓名:严行秋, 兵种:轻骑兵, 所属:马良队
姓名:卓重云, 兵种:轻骑兵, 所属:马良队
姓名:王勇申, 兵种:轻骑兵, 所属:马良队
姓名:邱协洽, 兵种:轻骑兵, 所属:马良队

"这样就可以用同样的方法去遍历不同的数据结构了,而又不需关注该对象的内部表示,是不是很方便?"小帅得意地说道。

使用java.util.Iterator

周仓不解道:“军师所言极是,不过,Java的List数据结构已经有现成的迭代器了,不用再重新实现了吧?”

小帅赞许道:“将军果然见识广啊,不错,Java的集合已经实现了Iterator接口,直接拿来用就可以啦!

我们改造下程序,直接把MaliangArmyIterator类删掉,把Iterator接口改成java.util.Iterator,然后调用ArrayList.iterator()方法就可以啦。

迭代器的优势

马良思考良久说:“Java已经有for循环遍历方式了,比起迭代器遍历方式,代码看起来更加简洁啊,那我们为什么还要用迭代器来遍历容器呢?迭代器的应用场景有哪些呢?”

小帅点点头:“你说的很有道理,我来说几点理由。“

首先,对于简单的数据结构直接使用for循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。

如果将这部分遍历的逻辑写到容器类中,就会增加容器类代码的复杂性,如果我们把树的前序遍历方式改成中序遍历方式,就需要修改容器类的代码。

容器类既要完成自己的本职工作(管理数据聚合),又要负责遍历,这就不符合一个重要的设计原则:单一职责原则。

单一职责原则:一个类或者模块只负责完成一个职责(或者功能)。

比如,针对图的遍历,我们就可以定义 DFSIterator、BFSIterator 两个迭代器类,让它们分别来实现深度优先遍历和广度优先遍历,我们可以将遍历操作拆分到迭代器类中,让容器类的职责更加单一。

其次,迭代器提供了接口,我们在实现代码的时候都是基于接口编程而不是基于实现编程,如果要添加新的遍历算法,我们只需要扩展新的迭代器类,也更符合开闭原则。

最后,如果你要在遍历时删除元素,你会怎么做?

如何在遍历时删除元素

这还不简单,直接在for循环中删除就好了呀,马良不加思索的说。

小帅笑道,那请将军来试试看。

马良随手写了一段代码,先打印再删除:

代码语言:javascript
复制
public class DeleteNormal {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<String>();
        lists.add("a");
        lists.add("b");
        lists.add("c");
        lists.add("d");
        lists.add("e");
        for(int i = 0; i < lists.size(); i++) {
            System.out.println(lists.get(i));
            lists.remove(i);
        }
    }
}

一运行却傻了眼:

代码语言:javascript
复制
a
c
e

结果怎么就和想象的不一样了呢?下面我们通过一个动画效果来看一下:

ArrayList的底层是数组,元素的删除会导致数组的移动,刚开始指针指向第0个元素“a",系统打印出了"a",删除了"a"之后,数组的所有元素向前移动一位,第0个元素就变成了"b"。

指针向后移一位后就直接指向了元素"c",系统打印出了"c",以此类推,最后删除了"e"之后,循环就结束了。

所以在遍历数组的时候添加或删除元素会导致不可预知的问题,那么如何在遍历的时候安全的删除元素呢?

没错,用迭代器就能做到:

代码语言:javascript
复制
public class DeleteIterator {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<String>();
        lists.add("a");
        lists.add("b");
        lists.add("c");
        lists.add("d");
        lists.add("e");
        Iterator listsIterator = lists.iterator();
        while (listsIterator.hasNext()) {
            System.out.println(listsIterator.next());
            listsIterator.remove();
        }
    }
}

输出:

代码语言:javascript
复制
a
b
c
d
e

那么迭代器是如何做到的呢?

我们来看一下源码:

原来在执行完ArrayList.this.remove(lastRet) 语句删除了元素之后,自动把指针重置到前一个元素上了,这就刚好抵消了数组的移动。

java.util.Iterator的安全机制

Java的Iterator接口使用起来更加安全,比如一个集合有两个迭代器的时候,其中一个迭代器删除了一个元素,为了避免出现不可预知的结果,另一个迭代器就会抛出异常,结束运行。

代码语言:javascript
复制
public class IteratorDemo {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<String>();
        lists.add("a");
        lists.add("b");
        lists.add("c");
        lists.add("d");
        lists.add("e");
        Iterator listsIterator = lists.iterator();
        Iterator listsIterator2 = lists.iterator();
        listsIterator.next();
        // 删除一个元素
        listsIterator.remove();
        // 集合变更后,抛出ConcurrentModificationException异常
        listsIterator2.next();
    }
}

集合中的元素变更后,第二个迭代器抛出ConcurrentModificationException异常:

代码语言:javascript
复制
Exception in thread "main" java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
 at java.util.ArrayList$Itr.next(ArrayList.java:851)
 at iterator.soldier.normal.IteratorDemo.main(IteratorDemo.java:20)

原来啊, AbstractList类中定义了一个成员变量 modCount,记录集合被修改的次数,集合每调用一次增加或删除元素的函数,就会给 modCount 加 1。

当通过调用集合上的 iterator() 函数来创建迭代器的时候,就会把 modCount 值传递给迭代器的 expectedModCount 成员变量。

之后每次调用迭代器上的 next()、remove() 函数,都会检查集合上的 modCount 是否等于 expectedModCount,也就是看,在创建完迭代器之后,modCount 是否改变过。

如果两个值不相同,那就说明集合存储的元素已经改变了,要么增加了元素,要么删除了元素,之前创建的迭代器已经不能正确运行了。

如果再继续使用就会产生不可预期的结果,为了防止意外,就直接抛出运行时异常结束运行,这是一种快速失败(fail-fast)的方法。

源代码截图如下:

总结

总结一下,迭代器模式有以下优点:

  • 迭代器模式封装集合内部的复杂数据结构,使用者不需要了解迭代器内部是如何遍历的,封装了复杂性;
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,简化了集合类,让两者的职责更加单一,符合单一职责原则;
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
  • 迭代器模式可以实现用不同的方式遍历同一个对象,还可以在一个对象上同时创建多个遍历器进行遍历。

另外,迭代器模式也是有缺点的,由于迭代器模式将存储数据和遍历数据的职责相分离,增加新的集合类后,就需要对应增加新的迭代器类,在一定程度上增加了系统的复杂性。

不过,如果只是简单的遍历需求,我们用系统自带的for循环就行啦!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程我也会 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引子
  • 迭代器模式
    • 使用java.util.Iterator
      • 迭代器的优势
        • 如何在遍历时删除元素
          • java.util.Iterator的安全机制
          • 总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档