引子
小帅在军中官至军师,身居高位,必然要尽心尽责,最近又要主动进行士兵普查,遂命各副将按各个兵种准备士兵名册。统领步兵的副将周仓,带队骑兵的副将马良,各自领命回去准备。
没过几日,周仓和马良就把各兵种的名册呈上来了,小帅翻开一看…
士兵类:
/**
* 士兵类
*/
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循环就行啦!