当前位置:网站首页>迭代器模式--沙场秋点兵
迭代器模式--沙场秋点兵
2022-06-11 10:54:00 【zhanyd】
引子
小帅在军中官至军师,身居高位,必然要尽心尽责,最近又要主动进行士兵普查,遂命各副将按各个兵种准备士兵名册。统领步兵的副将周仓,带队骑兵的副将马良,各自领命回去准备。
没过几日,周仓和马良就把各兵种的名册呈上来了,小帅翻开一看…
士兵类:
/** * 士兵类 */
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));
}
}
}
遍历结果:
姓名:华季鸣, 兵种:步兵, 所属:周仓队
姓名:春孟心, 兵种:步兵, 所属:周仓队
姓名:务孟晓, 兵种:步兵, 所属:周仓队
姓名:成仲爰, 兵种:步兵, 所属:周仓队
姓名:汉孟宝, 兵种:步兵, 所属:周仓队
姓名:达海青, 兵种:轻骑兵, 所属:马良队
姓名:严行秋, 兵种:轻骑兵, 所属:马良队
姓名:卓重云, 兵种:轻骑兵, 所属:马良队
姓名:王勇申, 兵种:轻骑兵, 所属:马良队
姓名:邱协洽, 兵种:轻骑兵, 所属:马良队
小帅立马发现了问题,责问到:怎么你们的士兵名单存储的方式都不一样啊?两个人都用不同的存储结构,遍历的方式都不一样,这样查起来太麻烦啦!
二人面露难色,马良上前说道:是我们疏忽大意,没有事先统一好,不过现在名册都已经做好,恐不好变更了。
迭代器模式
小帅沉思片刻,对他们说道,你们看,虽然你们用的遍历方法不一样,但是也有共同点:
- 判断集合中还有没有元素
- 取出元素

这样,我们可以抽象出一个迭代器来,下面我就来介绍下迭代器模式吧。
迭代器模式(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循环就行啦!
边栏推荐
- Common construction and capacity operation of string class
- 基于位置服务(LBS)的SSM的框架实现的兴趣社交软件平台设计与实现
- How programmers do sidelines
- 杰理之BLE 芯片供电范围及防烧芯片措施【篇】
- Leetcode 1961. Check whether the string is an array prefix
- string类的常见构造及容量操作
- Unity font spacing
- Design and implementation of interest social software platform based on location service (LBS) SSM framework
- 概率论:计算置信区间
- MySQL download, installation and use - complete and detailed steps
猜你喜欢

Taking the cooperation between different banks as an example, the construction of small program ecology

云开发mbti人格类型测试助手微信小程序源码
![Jerry's ble chip power supply range and anti burn chip measures [chapter]](/img/25/f35ca0366d31a70cd5e487347bb814.png)
Jerry's ble chip power supply range and anti burn chip measures [chapter]

Application of volatile in single chip microcomputer

MySQL下载安装使用-完整详细步骤

IIHS tsp+ annual safety list released: 7 EVs were selected, and there are common problems in pedestrian AEB

New Zealand is one of the best countries for road safety

微信云开发Al短视频一键换脸小程序源码

Writing the program into the microcontroller can control the forward and reverse rotation of the motor more conveniently and quickly

Cloud development MBTI personality type test assistant wechat applet source code
随机推荐
杰理之BLE SPP 开启 pin_code 功能【篇】
Metro roadmap cloud development applet source code and configuration tutorial
使用 Hystrix 实现微服务的容错处理
After 95, programmers in big factories were sentenced for deleting databases! Dissatisfied with the leaders because the project was taken over
Introduction and usage of Eval function
Report on various activity plans of safety month 2022 (28 pages)
Leetcode 1995. 统计特殊四元组(暴力枚举)
White screen time, first screen time
C language course design
MySQL optimized learning diary 10 - locking mechanism
MySQL (IX)
Application of volatile in single chip microcomputer
PHP仿网易云原创音乐分享平台网站源码
GameFi:您需要了解的关于“即玩即赚”游戏经济的一切
正大期货主账户预4 周三信息汇总
Rxjs Observable. Execute logical analysis of pipe passing in multiple operators
[games101] operation 2 -- triangle rasterization
34. find the first and last positions of elements in the sorted array ●●
When installing mysql, an error occurred because msvcr120 could not be found DLL, unable to continue code resolution "
SQL query statement optimization