1.什么是迭代器模式?
迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的实现。
2.通过实例需求理解迭代器模式
2.1 项目总监Review到问题
在做项目的时候,技术总监在review代码的时候发现了一个奇特的现象,两个员工对一个实体类进行封装时的用法不一样,一个使用List对实体对象进行存储,一个直接使用实体对象数组的形式,导致使用者必须针对两种不同的表示写出两段代码来适应。
如下所示:
从下面的代码我们可以看到,对于遍历DinerMenu和PancakeHouseMenu里的MenuItem的做法,由于使用不同的方式存储的,所以在使用的时候需要针对性的编写代码进行使用,这样很麻烦,不是吗?
package IteratorPattern.first;
import java.util.List;
public class FirstTest {
public static void main(String[] args) {
//针对DinerMenu的用法
DinerMenu dinerMenu = new DinerMenu();
MenuItem[] menuItems = dinerMenu.getMenuItems();
for(int i=0;i<dinerMenu.numberOfItems;i++){
System.out.println(menuItems[i].getName());
}
//针对PancakeHouseMenu的用法
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
List list = pancakeHouseMenu.getMenuItems();
for(Object obj:list){
MenuItem menuItem = (MenuItem) obj;
System.out.println(menuItem.getName());
}
}
}
这时,技术总监把这两个员工叫了过来,向他们说明了情况,他们也觉得这样对使用者很不友好,但是他们不知道该怎么去做(谁都不想改自己写好的代码)。
代码地址如下:
设计模式/src/main/java/IteratorPattern/first · 严家豆/设计模式 - 码云 - 开源中国 (gitee.com)
这时总监说到: 你们听说过迭代器模式吗? 迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的实现。
这样,我给你们一个接口,你们自己根据这个接口去修改代码,让使用者只需要了解这个接口就能轻易的使用你们的类:
两个员工无奈的接受了总监的意见,回去商量具体的方案了。
2.2 通过迭代器接口统一遍历方式
两个员工回去一起商量了下,于是迭代器模式的使用实例出来了:
如果按照上面的方式进行编写代码,那么使用者的代码就可以这样写:
import IteratorPattern.base.Iterator;
import IteratorPattern.first.MenuItem;
public class Alice {
/**
* 通过迭代器完成遍历
*/
public void printMenuByIterator(){
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
DinerMenu dinerMenu = new DinerMenu();
Iterator dinerIterator = dinerMenu.createIterator();
printMenu(pancakeIterator);
printMenu(dinerIterator);
}
/**
* 通过统一的迭代器方式进行遍历
* @param iterator
*/
private void printMenu(Iterator iterator){
while (iterator.hasNext()){
MenuItem item = (MenuItem) iterator.next();
System.out.println(item.getName()+" "+item.getPrice());
System.out.println(item.getDescription());
}
}
}
嗯嗯,技术总监满意的点了点头。 请针对下面的代码对迭代器模式再进行理解一次:
迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的实现。
代码地址:
设计模式/src/main/java/IteratorPattern/second · 严家豆/设计模式 - 码云 - 开源中国 (gitee.com)
这时,我们可以非常激动了,因为我们可以去看JDK中集合框架中的迭代器了!!!
2.3 JDK中的迭代器
首先,我们来看一下JDK中util包中对迭代器接口的定义:
我们可以看到,JDK的迭代器接口使用了泛型,这就意味着我们可以指定迭代器中的元素类型, 当我们指定类型时,在使用迭代器进行遍历时就可以不用进行转型操作了。
其中有四个方法接口:
- hasNexe: 用来判断迭代器中是否有下一个元素
- next: 获取下一个元素
- remove: 删除目前为止迭代器返回的最后一个元素
- forEachRemaining: 对剩余的每个元素执行给定的操作,直到所有元素都处理完毕或该操作引发异常
2.3.1 使用ArrayList中的迭代器实验
1)使用迭代器进行遍历
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(8);
arrayList.add(5);
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
2)使用迭代器进行删除
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(8);
arrayList.add(5);
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()){
Integer num = iterator.next();
if(num==8) iterator.remove();
}
System.out.println(arrayList);
}
3)使用迭代器对剩余的元素进行操作
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(8);
arrayList.add(5);
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()){
Integer num = iterator.next();
if(num==8) {
iterator.remove();
iterator.forEachRemaining(s->{
System.out.println(s+1);
});
}
}
}
2.3.2 ArrayList迭代器源码阅读
好啦,下面我们进行小试牛刀去看看ArrayList对迭代器的实现是怎样的:
下面我们一个一个方法的进行解读:
1)hasNext方法
public boolean hasNext() {
return cursor != size;
}
这里是通过将cursor和ArrayList维护大小的遍历size进行比对来判断是否还有下一个元素存在。
2)next方法
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
在获取下一个元素时,首先确保在使用迭代器的过程中不会有对此对象进行修改操作,否则抛出异常。
然后对cursor和size进行比较确保有效。
接着获得ArrayList的存放元素的数组elementData, 然后将cursor与elementData的长度进行比较确保有效。
然后取出位置为cursor的元素并将其值赋给lastRet.接着将cursor进行加一操作,返回元素。
3)remove方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
其实这个方法就是对lastReb索引上的元素进行删除操作。
其他的请大家自行查阅源码进行解读。