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

引子

小帅在军中官至军师,身居高位,必然要尽心尽责,最近又要主动进行士兵普查,遂命各副将按各个兵种准备士兵名册。统领步兵的副将周仓,带队骑兵的副将马良,各自领命回去准备。

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

士兵类:

/**
 * 士兵类
 */
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;
    }
}

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

/**
 * 周仓队
 */
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保存的:

/**
 * 马良队
 */
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;
    }

}

小帅开始遍历名册:

/**
 * 遍历士兵
 */
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));
        }
    }
}

遍历结果:

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

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

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

迭代器模式

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

  1. 判断集合中还有没有元素
  2. 取出元素
    在这里插入图片描述

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

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

迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。迭代器模式也叫做游标模式(Cursor Design Pattern)。

在这里插入图片描述
用了迭代器模式之后的代码:

迭代器接口:

public interface Iterator<E> {
    boolean hasNext();
    E next();
}

周仓队迭代器实现类:

/**
 * 周仓队迭代器实现类
 */
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;
    }
}

马良队迭代器实现类:

/**
 * 马良队迭代器实现类
 */
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;
    }

}

集合接口:

public interface ArmyCollection {
    Iterator createIterator();
}

周仓队实现类:

/**
 * 周仓队
 */
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);
    }

}

马良队实现类:

/**
 * 马良队
 */
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);
    }

}

遍历士兵:

/**
 * 遍历士兵
 */
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());
        }
    }
}

输出结果:

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

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

使用java.util.Iterator

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

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

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

迭代器的优势

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

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

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

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

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

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

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

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

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

如何在遍历时删除元素

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

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

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

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);
        }
    }
}

一运行却傻了眼:

a
c
e

结果怎么就和想象的不一样了呢?下面我们通过一个动画效果来看一下:
在这里插入图片描述

ArrayList的底层是数组,元素的删除会导致数组的移动,刚开始指针指向第0个元素“a",系统打印出了"a",删除了"a"之后,数组的所有元素向前移动一位,第0个元素就变成了"b",指针向后移一位后就直接指向了元素"c",系统打印出了"c",以此类推,最后删除了"e"之后,循环就结束了。

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

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

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();
        }
    }
}

输出:

a
b
c
d
e

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

我们来看一下源码:
在这里插入图片描述
原来在执行完ArrayList.this.remove(lastRet) 语句删除了元素之后,自动把指针重置到前一个元素上了,这就刚好抵消了数组的移动。

java.util.Iterator的安全机制

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

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异常:

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循环就行啦!

源码连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值