Iterator迭代器
在遍历集合的时候,会经常使用Iterator迭代器,其原理实现也较为简单,可以将其与单链表的实现进行对比理解。在Java中内置的Iterator接口中,主要是针对三个方法,如下所示:
-
boolean hasNext();
-
E next();
-
void remove();
其中hasNext判断后续是否还有元素可以进行迭代,也可以理解为后面还有没有元素,next函数用于获取当前元素并将指针后移一位,remove函数用于移除一个元素。
接下来就基于上述三种方法对Iterator迭代器进行学习,参考ArrayList中关于Iterator的源码进行分析。
成员变量
首先对Iterator接口的实现类中的一些成员变量进行定义,源代码如下所示。
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;
Itr() {}
}
根据Java API中的描述,三个成员变量分别具有以下含义:
- cursor:用于记录当前迭代器遍历到的位置,通过该属性访问元素,初始值为0
- lastRet:该属性用于记录上一次使用next()方法时遍历所在的位置,在调用remove方法后该属性值变回-1,初始值为-1
- expectedModCount:modCount是ArrayList的属性,用于记录对集合进行操作的次数,包括add、remove等方法。expectedModCount的应用是为了实现一种”快速失败"机制,即在使用迭代器的过程中如果对
原集合(如ArrayList)进行了修改,此时modCount的值将会发生变化,二者不再相同,此时将会抛出并发修改异常:ConcurrentModificationException
可以看出,对于Iterator来说,其中cursor和lastRet两个变量是最为重要的,其中cursor是用于指向元素位置的,从0开始,只会往前不会后退,在使用next()方法获取当前位置之后,cursor指针会自动往前加一,而lastRet也会更新为之前的cursor值。根据上述这些描述,在不考虑remove的情况下,遍历的情形应该是这样的。
hasNext()函数
通过查看ArrayList中的源码得知,hasNext()的源码如下所示:
public boolean hasNext() {
return cursor != size;
}
通过上述代码可以得知,判断方法也较为简单,其中size为当前集合的数据长度,将集合中的元素当作数组来考虑的话,那么对于一个长度为size的数组,有效范围就是0到size-1,由于cursor永远指向下一个元素的位置,故当cursor等于size的时候,说明之前访问的元素已经是最后一个元素了,那么此时就没有下一个元素了。
next()函数
通过查看ArrayList中的源码得知,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使用变量i进行保存,如果i越界了,就需要抛出异常,之后就是前面提到的,返回cursor当前指向的元素,然后将lastRet设置为cursor的值,最后将cursor的值加一,也就是指向下一个元素。上述代码执行过程和原理解释部分顺序不太相同,但是实质是一样的。
remove()函数
通过查看ArrayList中的源码得知,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的合法性,由于lastRet初始值为-1,从这里也可以看出,至少要执行一次next()函数才能执行remove()函数,否则会抛出异常。
在判断了lastRet的合法性之后,接下来就是直接删除lastRet指向的元素,这个元素也就是前面刚刚访问过的元素。删除之后由于各个元素的位置会发生变化,所以cursor需要后退一格,这里使用cursor = lastRet来让cursor回退。同时将lastRet置为-1,表示刚刚进行了删除操作。
总结
Iterator实现类中的一些方法在理解时可能会很绕,不妨换一种角度,lastRet指向刚刚访问过的元素,初始化lastRet为-1,即代表不指向任何元素,next指向下一个要访问的元素,初始化为0,表示第一个元素。
hastNext()则是判断是否还有后续元素。
next()则是返回下一个元素,同时更新lastRet和cursor,例如当通过next()函数返回了对象“abc”之后,lastRet就指向对象“abc”,next()则是指向元素“abc”下一个元素。
remove()则是删除当前元素,即remove()删除的只是对于刚刚访问过的元素,也就是lastRet指向的元素进行操作,而不是对于cursor指向的元素进行操作,删除之后直接将lastRet置为-1。这也就代表了每一次remove()之前都需要调用至少一次next()方法。