迭代器模式定义
就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。这意味着迭代器需要提供统一的接口。
普通访问
我们先来看下正常访问集合
访问数组
int array[] = new int[3];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
访问List
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ; i++) {
String string = list.get(i);
}
我们可以看出,以上两种方式,我们总是知道集合的内部结构。访问集合元素的代码是和集合本身紧密耦合的。无法将访问遍历逻辑从集合类客户端代码抽离出来。不同的集合会有不用的遍历代码。所以才有Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端不用直接和集合进行打交道,而是控制Iterator向它发送向前向后的指令,就可以遍历集合。
Java迭代器
- java.util.Iterator
先看下迭代器接口的定义
package java.util;
public interface Iterator<E> {
boolean hasNext();//判断是否存在下一个对象元素
E next();//获取下一个元素
void remove();//移除元素
}
2.Iterable
Java中还提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器,我们常用的实现了该接口的子接口有:Collection、List、Set等。该接口的iterator()方法返回一个标准的Iterator实现。实现Iterable接口允许对象成为Foreach语句的目标。就可以通过foreach语句来遍历你的底层序列。
Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。
Package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
Iterator<T> iterator();
}
Java中几乎所有的集合都直接或间接提供了遍历本集合的迭代器实现。其作为内部类存在于集合类内部。下面我们看一个最简单的迭代器实现——ArrayList的迭代器。
ArrayList迭代器
在ArrayList内部类Itr实现了Iterator接口,提供next() hasNext() remove()等方法。先看下维护的几个变量
private class Itr implements Iterator<E> {
//下一个元素的位置下标
int cursor; // index of next element to return
//上一个元素的位置下标
int lastRet = -1; // index of last element returned; -1 if no such
//预期被修改的次数值,初始值等于modCount
//modCount是ArrayList维护的变量,当进行list.add()/remove()操作时会修改这个值
int expectedModCount = modCount;
.....
}
- 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];
}
首先进行检查,checkForComodification()是Itr内部函数,如下
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
检查期待修改次数和真正修改次数modCount是否相等,不相等抛出异常
接下来获取下一个元素位置下标cursor进行判断,逻辑很简单不解释。最终取出元素数据数组中相应值返回并将cursor递增。同时将lastRet递增
- 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();
}
}
首先判断lastRet是否小于零,小于零则抛出异常。由于初始值为-1并且只有在next()方法中才操作lastRet变量,所以迭代时不next()而直接remove()会报错,这点也很好理解。然后是进行checkForComodification()。接着调用集合remove()方法。并将expectedModCount重新赋值,这点很重要。这也是为什么通过迭代器遍历List时,使用迭代器remove()方法删除元素没有问题,通过list.remove()删除就会报错。后边会详细解释。
- 外部调用
List<String> list=new ArrayList<>();
list.add("abc");
list.add("edf");
list.add("ghi");
for(Iterator<String> it=list.iterator();it.hasNext();)
{
System.out.println(it.next());
}
关于删除元素
有这样一个问题,需要我们思考:在迭代时如何正确删除元素
方式一:size()遍历调用list.remove()删除
list.clear();
list.add("a");
list.add("b");
list.add("b");
list.add("a");
list.add("a");
/**
* 当remove()一个元素后,后面的的元素会集体向前移动,这样删除掉的元素的下一个
* 元素会移动到当前位置,但此位置已经循环过了,所以会漏掉该元素的循环,log如下:
*init[a, b, b, b, a],i=0
* init[a, b, b, b, a],i=1
* after delete:[a, b, b, a]
* init[a, b, b, a],i=2
* after delete:[a, b, a]
* result:[a, b, a]
*/
for(int i=0;i<list.size();i++){
System.out.println("init"+list.toString()+",i="+i);
if(list.get(i).equals("b")){
//list.remove(i); 与list.remove(obj)同效果
list.remove("b");
System.out.println("after delete:"+list.toString());
}
}
方式二:迭代器遍历调用list.remove()
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
结果:
报ConcurrentModificationException()错误。因为在list.remove()时会修改modCount()值,如下:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
modCount就和创建Iterator()时值不相等,那么再次调用Itr.next()时进行checkForComodification()检查,就会报错。可以再回头看下next()代码
方式三:通过Itr.remove()方式删除
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意这个地方
}
正常删除,因为在Itr.remove()时会更新expectedModCount值
后记
Java中的集合(Collection Map)中,每一个集合都实现了自己的迭代器,这样外部访问时可以采用统一的接口。而实现却是每一个集合中不同的实现。这种模式值得我们在开发中学习。