一、阐述
迭代器接口 Iterator
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
该接口有三个方法:
hasNext()
:判断容器内是否还有更多的元素next()
:返回迭代器刚越过的元素的引用,返回值是 Objectremove()
:删除迭代器刚越过的元素
二、ArrayList中的Iterator迭代器
因为 ArrayList 继承了 AbstractList 类,因此,ArrayList 中的 Iterator 方法实际上是重写了 AbstractList 中的 Iterator 方法
我们直接来看 AbstracList 类中重写后的该方法
public Iterator<E> iterator() {
// 实例化 Itr 类的对象
return new Itr();
}
这个 Itr 是 AbstractList 类中的内部类,实现了 Iterator 接口
// Itr 是 AbstractList 的内部类
private class Itr implements Iterator<E> {
// 下一个元素的索引位置,默认为 0
int cursor = 0;
// 上一个元素的索引位置,默认为 -1
int lastRet = -1;
// 期望的修改次数
int expectedModCount = modCount;
......
}
在来看看 Itr 这个内部类中的具体的方法,hasNext()
方法用于判断对于当前位置的元素,是否还有下一个元素。判断的标准是,如果下一个元素的索引位置不等于集合的长度,就有下一个元素,反过来,就没有下一个元素,因为元素的索引位置比元素长度小 1,当下一个元素的索引位置等于集合的长度,那么此时当前元素便是集合的最后一个元素了,肯定不能有下一个元素了
public boolean hasNext() {
return cursor != size;
}
三、next 方法
来看 next()
方法,先不看里面的 checkForComodification()
方法,直接看寻找下一个 ArrayList 中的元素的过程
public E next() {
checkForComodification();
// 使用变量 i 存放下一个元素的索引位置
int i = cursor;
// 如果下一个元素的索引位置大于集合的长度, 抛出异常
if (i >= size)
throw new NoSuchElementException();
// 获取 ArrayList 集合的底层数组
Object[] elementData = ArrayList.this.elementData;
// 如果下一个元素的索引位置大于底层数组的长度, 抛出异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 当前索引的位置向后移动一位
cursor = i + 1;
//
return (E) elementData[lastRet = i];
}
可以看到,当执行 next 方法的时候,每次获取的都是 当前位置 cursor 的前一个位置 lastRet 上的值,cursor 则是用来判断下一个位置还有没有元素的,好及时停止遍历
举一个例子来看一下 next 的具体流程
我们以上图为例,通过图示进一步的去看是如何执行的
四、remove 方法
在来看一下 remove() 方法
public void remove() {
// 因为是根据 lastRet 的位置来删除元素的,因此不能是负数,此时会抛出异常
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 删除数组在 lastRet 对应的元素,
ArrayList.this.remove(lastRet);
// cursor 指向它的前一个位置 lastRet
cursor = lastRet;
// 将 lastRet 指向 0 之前的位置
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public E remove(int index) {
// 判断当前位置是否大于集合的大小
rangeCheck(index);
modCount++;
// 获取数组在 index 位置对应的元素
E oldValue = elementData(index);
// 获取删除元素后需要复制的数组的长度
int numMoved = size - index - 1;
if (numMoved > 0)
// 复制旧数组到新数组
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 集合长度减1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
可以看到,每次删除当前位置 cursor 的前一位 lastRet 对应的元素,即刚刚越过的那个元素,每当删除完毕,当前位置 cursor 回退到被删元素所在的位置,而 lastRet 则退回到位置 -1,等待下一次执行 next() 方法
通过一个例子来执行一下整个遍历的过程
通过图示来看一下整个删除的过程
其中删除 remove() 的过程我其实是省略了一个步骤,应该包括两个步骤,分别是删除元素和移动两个引用的值,比如第一次执行 remove 方法,其实是执行下面两个操作:
- 执行 ArrayList.this.remove(lastRet) 方法,删除位置 lastRet 对应的元素 “AAA”
- 将 cursor 向前移一个位置,将 lastRet 移到位置 -1
这个时候,cursor 指向的是元素 “BBB”,即被删除元素 “AAA” 的后一个元素,然后开始之后的执行
五、注意点
public class ArrayListTest01 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("AAA");
arrayList.add("BBB");
arrayList.add("CCC");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
iterator.remove();
String next = iterator.next();
System.out.println(next);
}
}
}
结果是:
Exception in thread "main" java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:844)
at edu.just.failfast.ArrayListTest01.main(ArrayListTest01.java:17)
在使用迭代器遍历的时候,如果一开始没有执行迭代器的 next() 方法,而是直接执行了迭代器的 remove() 方法,此时会报错
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
...
}
因为在创建迭代器之后,cursor 默认为 0,lastRet 默认为 -1,根据源代码,此时便会报错了,其实想想也是,remove 方法删除的是刚刚“越过”的那个元素,而这个时候啥也没有越过,肯定就会报错了
六、参考
https://www.cnblogs.com/dolphin0520/p/3933551.html
https://www.cnblogs.com/expiator/p/6125141.html
https://blog.youkuaiyun.com/u011240877/article/details/52743564