我们可以对一个数组进行如下的迭代遍历
int[] array = {1,2,2,3,4,6,7};
for (int i=0;i<array.length;i++){
System.out.println(array[i]);
}
对于array,我们直接用访问下标的方式对他进行遍历,因为我们知道他是一个数组,就像遍历一个链表用.next()进行遍历,因为我们知道.next()指向了下一个元素。或者对与这个容器
public class MyIntegerContainer{
private Integer[] array;
public MyIntegerContainer(Integer[] array){
this.array = array;
}
public Integer[] getDate(){
return array;
}
}
我们进行如下遍历
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,6,5,4,8,9,6,5,6,6};
MyIntegerContainer myContainer = new MyIntegerContainer(array);
Integer[] date = myContainer.getDate();
for (int i =0;i<date.length;i++){
System.out.println(date[i]);
}
}
}
上边对与数组,链表,容器进行的遍历,都有一个前提——我们知道他的内部结构。刚开始编程的时候并不觉得这有什么不妥,但随着时间的推移慢慢意识到,我只是想要遍历给定容器内部的元素,他具体怎么存储跟我又有什么关系呢?所以,必定需要一种更好的设计来解决这个问题。这个更好的设计我们称之为——迭代器模式!
迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。 作为一种设计模式,在各种语言中都有对应的应用,今天主要说的就是java中对迭代器模式的应用(想说说其他地方的也不敢说,怕说错…)!java提供了两个接口 Iterator和Iterable。
public interface Iterator<E> {
boolean hasNext();//是否还有下一个元素
E next();//返回下一个元素
...
}
public interface Iterable<T> {
Iterator<T> iterator();//返回一个迭代器
}
这两个接口的用途一目了然,Iterator用来判断这个容器有无下一个元素,以及返回下一个元素,Iterable用来返回一个迭代器。举个列子更能明白这两个接口是怎么使用的,下面我们就对上边的容器类MyIntegerContainer进行改造
public class MyIntegerContainer implements Iterable{
private Integer[] array;
public MyIntegerContainer(Integer[] array){
this.array = array;
}
public Integer[] getDate(){
return array;
}
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator{
int cursor; // index of next element to return
@Override
public boolean hasNext() {
if (cursor>array.length-1)return false;
return true;
}
@Override
public Integer next() {
return array[cursor++];
}
}
}
我们首先让容器类实现了Iterable接口,从而可以返回一个迭代器,这个迭代器从何而来呢?我们又定义了一个内部类MyIterator,这个内部类实现了接口Iterator,从而对容器内部的元素进行访问的任务就交给了他,也就是这个内部类扮演了迭代器的角色。众所周知,foreach循环内部就是用迭代器对容器的元素进行迭代的,如果一个容器不提供迭代器是不能使用foreach的,于是我们来验证一下我们改造的结果
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,6,5,4,8,9,6,5,6,6};
MyIntegerContainer myContainer = new MyIntegerContainer(array);
for(Object a :
myContainer) {
System.out.println(a);
}
}
}
完美打印出了容器的中的元素,就此改造工作完成,我们可以在不了解容器内部细节的前提下实现对容器内元素的迭代,不管内部是数组,是链表,还是其他各种形式,只要提供了迭代器,我们就不需要了解容器内部的具体存储形式。 可以发现,JDK中好多容器中都实现了这个套路,只不过要更复杂一些,对安全性有更周全的考虑。我们用ArrayList内部的一个迭代器实现来举例:
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
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
这个迭代器中有一个方法,也就是上边代码块的最后一个方法checkForComodification(),这个方法检测modCount是否等于expectedModCount,如果不等的话就会抛出异常。modCount是在容器中定义的一个变量,初始值是0,每当对容器元素进行一次添加或删除的时候,modCount就会加1,modCount就像一个版本号,当我们对容器开始进行迭代的时候,迭代器会用expectedModCount记录下这个版本号,并且在迭代的过程中一直对两个变量的相等关系进行检测,一旦发现两个元素不等,迭代器就会抛出异常,终止这次遍历,这个机制称为——快速失败(fail-fast)!为什么要这样做呢?因为当两个值不等的时候,一定是modCount发生了变化(一般是其他线程对容器进行了删改),也就是容器内容发生了变化,此时的遍历被认为是不安全的遍历。也就是说使用迭代器对容器进行遍历的时候不允许其他线程对容器进行操作,但是,对与迭代器本身是可以对容器进行操作的,可以看到迭代器有这样一个方法remove(),这个方法内部调用了容器的remove(),但同时对expectedModCount进行了同步,所以不会触发快速失败机制。