设计模式之美学习-行为型-迭代器模式(三十一)
什么是迭代器模式
迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。
一个完整的迭代器模式 设计容器(数组、链表、树、图、跳表)和迭代器
为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类
类图
迭代器接口定义
// 接口定义方式一 public interface Iterator<E> { //是否还有迭代元素 boolean hasNext(); //指针向前移动一位 void next(); //返回当前指针指向的元素 E currentItem(); } // 接口定义方式二 public interface Iterator<E> { //是否还有迭代元素 boolean hasNext(); //指针向前移动一位 并返回对象 E next(); }
代码实现
//迭代器模式的实现类 public class ArrayIterator<E> implements Iterator<E> { private int cursor; private ArrayList<E> arrayList; public ArrayIterator(ArrayList<E> arrayList) { //当前指针 this.cursor = 0; //迭代容器 this.arrayList = arrayList; } @Override public boolean hasNext() { return cursor != arrayList.size(); //注意这里,cursor在指向最后一个元素的时候,hasNext()仍旧返回true。 } @Override public void next() { //向前移动一位 cursor++; } @Override public E currentItem() { //犯规当前指向元素 if (cursor >= arrayList.size()) { throw new NoSuchElementException(); } return arrayList.get(cursor); } } public class Demo { public static void main(String[] args) { ArrayList<String> names = new ArrayList<>(); names.add("xzg"); names.add("wang"); names.add("zheng"); //创建迭代器 并为迭代器设置容器 Iterator<String> iterator = new ArrayIterator(names); while (iterator.hasNext()) { System.out.println(iterator.currentItem()); iterator.next(); } } }
java容器代码实现
java是容器实现获得迭代器的接口 在类里面new 迭代器
//容器 public interface List<E> { //定义获取迭代器接口 Iterator iterator(); //...省略其他接口函数... } public class ArrayList<E> implements List<E> { //... //返回迭代器 public Iterator iterator() { return new ArrayIterator(this); } //...省略其他代码 } public class Demo { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("xzg"); names.add("wang"); names.add("zheng"); //使用 foreach语法糖 编译后也是通过迭代器 Iterator<String> iterator = names.iterator(); while (iterator.hasNext()) { System.out.println(iterator.currentItem()); iterator.next(); } } }
好处是,将迭代和容器分离,复杂的容器可能会有多种迭代方式,如果都写在容器里面增加融洽的复杂度
如:有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等
迭代过程中删除和新增元素
删除元素
可能导致遍历不到的问题 以上面ArrayIterator 为例子
public class Demo { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("a"); names.add("b"); names.add("c"); names.add("d"); Iterator<String> iterator = names.iterator(); iterator.next(); names.remove("a"); } }
情况一
遍历前删除第一个元素 对遍历结果不会有影响
情况二
当遍历到第二个元素 删除元素a 数组重排 导致遍历不到
情况三
遍历过程中 删除指针后面的元素 不会有影响
插入元素
遍历到b 在a前面插入元素 导致a重复遍历
如何解决重复遍历和未遍历不到问题
方案1:遍历过程中不能新增删除元素
方案2:遍历过程中 新增删除元素 遍历报错(java就是采用这种实现)
java的解决方案
在 ArrayList 中定义一个成员变量 modCount,记录集合被修改的次数,集合每调用一次增加或删除元素的函数,就会给 modCount 加 1。当通过调用集合上的 iterator() 函数来创建迭代器的时候,我们把 modCount 值传递给迭代器的 expectedModCount 成员变量,之后每次调用迭代器上的 hasNext()、next()、currentItem() 函数,我们都会检查集合上的 modCount 是否等于 expectedModCount,也就是看,在创建完迭代器之后,modCount 是否改变过
public class ArrayIterator implements Iterator { private int cursor; private ArrayList arrayList; private int expectedModCount; public ArrayIterator(ArrayList arrayList) { this.cursor = 0; this.arrayList = arrayList; //存储arrayList的expectedModCount this.expectedModCount = arrayList.modCount; } @Override public boolean hasNext() { //检查集合是否改变 checkForComodification(); return cursor < arrayList.size(); } @Override public void next() { //检查集合是否改变 checkForComodification(); cursor++; } @Override public Object currentItem() { //检查集合是否改变 checkForComodification(); return arrayList.get(cursor); } private void checkForComodification() { //集合发生改变报错 if (arrayList.modCount != expectedModCount) throw new ConcurrentModificationException(); } } //代码示例 public class Demo { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("a"); names.add("b"); names.add("c"); names.add("d"); Iterator<String> iterator = names.iterator(); iterator.next(); names.remove("a"); iterator.next();//抛出ConcurrentModificationException异常 } }
为什么通过迭代器可以删除元素
迭代器类新增了一个 lastRet 成员变量,用来记录游标指向的前一个元素。通过迭代器去删除这个元素的时候,我们可以更新迭代器中的游标和 lastRet 值,来保证不会因为删除元素而导致某个元素遍历不到
删除元素下标大于cusor什么都不做
伤处元素下标小于cusor cusor-1 lastRet-1