Iterator大家应该都很熟悉了,作为Java程序员的我们来说,遍历集合这也是我们刚开始学习Java知识。
遍历集合的方式也有很多,比如for循环、while循环、foreach循环、Iterator等。这里的Iterator就是我们设计模式里面的迭代器模式。
这次要跟大家分享的设计模式就是这迭代器模式,虽然很多语言都直接把Iterator封装到基础工具类中,但是它的特性你都了解吗?
设计模式系列往期文章:
迭代器大家都很熟悉,那么什么叫迭代器?它的目的又是什么呢?
解析图:
整个图看起来其实就两个东西,一个容器,一个迭代器。
这次就不举列了。直接手写一个迭代器,我们再测试一下。
主要还是理解迭代器到底是干嘛用的:
能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素
话不多说,还是直接上手撸代码
public interface Aggregate {
// 添加元素
void add(Object object);
// 移除元素
void remove(Object object);
// 迭代器
Iterator iterator();
}
按照上面的类图,先是创建抽象容器,定义几个基本添加删除元素方法,以及迭代器
public interface Iterator<E> {
// 判断容器是否有值
boolean hasNext();
// 把游标执向下一个指针
void next();
// 当前遍历的数据
E currentItem();
}
其次 再试创建抽象迭代器,遍历容器中的数据
public class ConcreteAggregate implements Aggregate {
private ArrayList arrayList = new ArrayList();
@Override
public void add(Object object) {
this.arrayList.add(object);
}
@Override
public void remove(Object object) {
this.arrayList.remove(object);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(this.arrayList);
}
}
开始定义我们具体的容器了,内部定一个ArrayList容器,用来存放数据,当然这里大家也可以改成其他的容器 比如说用Vector 或者其他的 栈、 树、 图 等
public class ConcreteIterator<E> implements Iterator<E> {
private int cursor; // 游标
private ArrayList arrayList;
public ConcreteIterator(ArrayList arrayList) {
this.cursor = 0;
this.arrayList = arrayList;
}
@Override
public boolean hasNext() {
if (this.cursor == this.arrayList.size()) {
return false;
}
return true;
}
@Override
public void next() {
cursor++;
System.out.println(cursor + " cursor");
}
@Override
public E currentItem() {
if (cursor >= arrayList.size()) {
throw new NoSuchElementException();
}
E e = (E) arrayList.get(cursor);
this.next();
return e;
}
// 测试demo
public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate();
aggregate.add("java");
aggregate.add("c++");
aggregate.add("php");
aggregate.add("敖丙");
Iterator iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.currentItem());
}
// 结果:1 java
// 2 c++
// 3 php
// 4 敖丙
}
}
最后就是实现具体的迭代器了, 在currentItem里面根据遍历的游标,获取数组里面的值
同时在main方法里面就是测试demo了,以上就是简单的手撸迭代器了。
这里面我们其实还可以有其它的各种特别的玩法,比如说怎么实现暂停遍历等,只有了解内部实现,我们才能改造出符合当前所需要的业务代码。
在Java的中也有迭代器,java.util.Iterator类以及java.util.Collection,就是典型的迭代器喝容器的列子,接下来看看具体的源码
当next没有值的时候则会抛出NoSuchElementException异常信息,上面的手撸异常也是根据这个来的
在Java中 常见的 List、Set、Queue都是extend Collection(容器),而Collection又定义迭代器Iterator,这就是能直接使用的原因了。
上面我们看完了Java中的迭代器,不知道,大家注意了没有,我们在使用迭代器的时候是不能再对集合进行增减操作的,否则就会抛出ConcurrentModificationException异常
那么问题来了,为什么会有这个异常信息呢?
看过ArrayList源码的同学都知道底层是数据结构中的数组结构的,所以我们看下接下来图结构
假设现在开始在遍历当前这个数组,当从第一步执行到第二步,都是正常运行的,假设现在执行完第二步,开始走第三步时
删除 java这个元素,数组为了保持存储数据的连续性,当删除java数据时,是会发生数组元素的迁移的。所以正常步骤3应该是遍历到aobing元素的变成当前数组元素已经是ao bing了。
导致了数组会发生ao bing没有遍历到,因为数据迁移而丢失了。
同样的假设在后面添加元素按照向后迁移,还能遍历到,那如过插入的数据是在已经遍历的之前呢?
这样整个遍历就变成不可预估了。
public static void main(String[] args) {
List<String> aggregate = new ArrayList();
aggregate.add("java");
aggregate.add("c++");
aggregate.add("php");
aggregate.add("敖丙");
Iterator<String> iterator = aggregate.iterator();
while (iterator.hasNext()) {
iterator.remove(); // 添加这行代码 java.lang.IllegalStateException
System.out.println(iterator.next());
iterator.remove(); // 正常
}
}
再来看这个测试demo,同样都是调用remove方法,不同的地方结果不一样,这也就是刚好印证上面的图体现的问题,所以要解决这个问题,要么就是遍历的时候不允许增删元素,要么是增删元素之后让遍历报错。
通过上面的列子已经了解了迭代器的原理以及实现,大家可以根据自己所需要的场景改造迭代器,很多公司的一些自己的框架或者工具类等等都是通过现有框架源码进行改造而来。
迭代器的优点:
迭代器设计模式在我们业务场景中自己写的代码中 我个人是觉得比较少见的,至少到目前我还没有怎么发现有好的业务场景可以用这个模式,所以这里我就不给大家举例业务代码改造了。(毕竟不能因为设计模式而强行设计)
跟大家分享迭代器主要是想让大家了解Java集合遍历怎么实现的,方便我们提升自己以后的看源码的能力,以及提升自己的设计能力。
后面就再跟大家再聊聊动态代理设计模式,就不会再详细讲了其他的模式了,因为本身不怎么常见,作为了解还是会和大家做一个总结分享。
今天的迭代器模式到此结束,我是敖丙,你知道的越多,你不知道的越多,我们下期见!!!