先来看下面的示例:
public class Demo {
public static void main(String[] args) throws IOException {
List<String> list = new LinkedList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
list.add("小白龙");
ListIterator<String> iterator = list.listIterator();
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
// System.out.println(iterator.next());// 会抛出NoSuchElementException异常
}
}
/**
* 打印结果:
* 唐僧
* 孙悟空
* 猪八戒
* 沙僧
* 小白龙
*/
那么该迭代器的原理到底是怎样的?查看该类的源码:
/**
* ListIterator<E>迭代器的实现类
*/
private class ListItr implements ListIterator<E> {
// 全局变量,上一次执行 next() 或者 previos() 方法时的节点
private Node<E> lastReturned;
// 后继结点
private Node<E> next;
// 后继结点的索引
private int nextIndex;
// 将修改次数modCount赋给expectedModCount
// modCount是指实际修改次数
// expectedModCount是指期望修改次数
private int expectedModCount = modCount;
/**
* 带参构造器
* @param index 指定索引
*/
ListItr(int index) {
// assert isPositionIndex(index);
// 对next和nextIndex进行赋值,所以next就是根据索引index查询出来的结点
next = (index == size) ? null : node(index);
nextIndex = index;
}
/**
* 判断是否还有下一个结点
* @return 如果还有后继结点则返回true,否则返回false
*/
public boolean hasNext() {
// nextIndex表示当前结点的索引
// size表示元素的实际个数
// 如果nextIndex小于size则表示仍然还有后继结点,如果大于等于size那么表示要么是尾结点,要么索引越界了
return nextIndex < size;
}
// 取下一个元素
public E next() {
// 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
checkForComodification();
// 2. 判断是否有下一个元素,如果没有则抛出NoSuchElementException异常
if (!hasNext()) // 表示hashNext()为false才会执行
throw new NoSuchElementException();
// 3. 保存next结点
lastReturned = next;
// 4. 迭代器指向下一个结点
next = next.next;
// 5. 索引加1
nextIndex++;
// 6. 返回旧next结点的内容
return lastReturned.item;
}
/**
* 判断是否有前驱结点
* @return 如果有前驱结点返回true,否则返回false
*/
public boolean hasPrevious() {
// 即判断nextIndex是否大于0
return nextIndex > 0;
}
/**
* 获取前驱结点
* @return 返回前驱结点
*/
public E previous() {
// 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
checkForComodification();
// 2. 判断是否有上一个元素,空表或只有一个元素都没有前驱结点,如果没有则抛出NoSuchElementException异常
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
/**
* 返回下一个结点的索引
* @return 下一个结点的索引值,从0开始的
*/
public int nextIndex() {
// 直接返回nextIndex即可
return nextIndex;
}
/**
* 返回前驱结点的索引
* @return 前驱结点的索引
*/
public int previousIndex() {
// 即nextIndex减去1的结果
return nextIndex - 1;
}
/**
* 使用迭代器进行迭代的时候不能进行调用list.remove()或list.add()删除修改元素,否则会抛出ConcurrentModificationException异常
* 所以如果要增加或删除元素需要使用迭代器Iterator内部的remove()和add()方法
*/
public void remove() {
// 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
checkForComodification();
// 2. 判断lastReturned是否为null来判断迭代器的状态
if (lastReturned == null)
throw new IllegalStateException();
// 3. 获取上一个结点的next结点的next结点,就是当前结点的后继结点
Node<E> lastNext = lastReturned.next;
// 4. 删除当前结点
unlink(lastReturned);
if (next == lastReturned)
// 重新设置next结点,该指向被删除结点的下一个结点
next = lastNext;
else
nextIndex--;
// 将lastReturned置为null,便于回收
lastReturned = null;
// 同时expectedModCount修改次数加1
expectedModCount++;
}
/**
* 修改结点的值
* @param e 新值
*/
public void set(E e) {
// 1. 检查迭代器的状态
if (lastReturned == null)
throw new IllegalStateException();
// 2. 检查在迭代器进行迭代时是否修改了List集合
checkForComodification();
// 3. 直接修改当前结点的item属性值
lastReturned.item = e;
}
/**
* 添加结点
* @param e 待添加的结点内
*/
public void add(E e) {
// 1. 检查在迭代时是否有修改List集合
checkForComodification();
// 2. 将lastReturned置为null
lastReturned = null;
// 3. 判断next是否为null
// 3.1 如果为null,表示next是尾结点,那么将结点添加在末尾即可
if (next == null)
linkLast(e);
// 3.2 表示不为null,那么插入在next结点之前
else
linkBefore(e, next);
// 4. 收尾处理
// 4.1 nextIndex需要加1
nextIndex++;
// 4.2 由于添加了元素,expectedModCount也需要加1
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
/**
* 验证modCount的值和expectedModCount的值是否相等,所以当你在调用了ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,而迭代器中的expectedModCount未同步,因此才会导致再次调用Iterator.next()方法时抛出异常。
*/
final void checkForComodification() {
// 本质上判断modCount是否等于expectedModCount
if (modCount != expectedModCount)
// 如果不相等表示在迭代时调用了list.add()或list.remove(),那么抛出此异常
throw new ConcurrentModificationException();
}
}
其实我们最应该关注的是next()方法,查看下图
从上图可以知道在创建迭代器时,next和nextIndex已经有内容了,看获得ListIterator迭代器的listIterator()方法
其中调用了ListItr构造器,默认索引index为0
所以在调用next()方法时做了三件事:
- 保存next结点,返回该结点的item内容
- 将next结点指向下一个结点
- nextIndex索引加1
注意上面Demo.java中的代码,当第六次调用next()时将抛出NoSuchElementException异常,从next()源码中我们可以查看它首先要判断是否还有下一个元素。
没有下一个元素则抛出该异常。
所以我们如果要遍历迭代器,应该如下:
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
但在遍历时可能遇到一个问题,如果你想删除或增加元素怎么办,看下面的代码:
public class Demo {
public static void main(String[] args) throws IOException {
List<String> list = new LinkedList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
list.add("小白龙");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String next = iterator.next();
if(next.equals("孙悟空")){
list.remove("孙悟空");
}
}
}
}
那么运行就会报错
看抛出这个异常的源代码:
原来如果modCount与expectedModCount不相等就会抛出异常。
因为在你迭代之前,迭代器已经被通过list.itertor()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,那么Java就会给出异常。因为此时Iterator对象已经无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出ConcurrentModificationException异常)。
那么modCount与expectedModCount是怎么回事呢?
- modCount是在AbstractList中定义的一个变量,表示当前集合的增删次数,初始值为0,而LinkedList间接继承自AbstractList类,所以也有该属性,在该变量在对List进行添加(list.add())或删除(list.remove())操作时进行加1处理,表示对集合进行了修改。
- expectedModCount是在ListItr类中定义的一个变量,表示当前迭代器的增删次数,该类实现了ListIteratror<E>接口,初始值为modCount,在调用该迭代器内部的添加和删除方法时才变化,平时不变化。
在上面代码中我们在迭代器中调用了LinkedList类的add()或remove()方法,只更新了modCount值,而迭代器中的expectedModCount没有更新,所以会抛出异常。
但如果调用ListIterator内部的remove()或add()方法就不会抛出此异常,因为同时更新了expectedModCount和modCount的值,如下源码能够证明:
使用的目的为了实现快速失败机制(fail-fast),在Java集合中有很多集合都实现了快速失败机制。
快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
看下面的例子就是多线程产生快速失败:
public class Demo {
public static List<String> list = new LinkedList<>();
public static void main(String[] args) throws IOException {
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
list.add("小白龙");
new Thread(new Runnable() {
@Override
public void run() {
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
list.remove("猪八戒");
}
}).start();
}
}
所以无论是单线程还是多线程为了避免抛出ConcurrentModificationException异常,也为了能够在使用迭代器遍历集合时对集合中元素进行增删操作,可以使用迭代器内部的remove()或add()方法。
public class Demo {
public static List<String> list = new LinkedList<>();
public static void main(String[] args) throws IOException {
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
list.add("小白龙");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("猪八戒")) {
iterator.remove();
}
}
for (String s : list) {
System.out.println(s);
}
}
}
/**
* 打印结果:
* 唐僧
* 孙悟空
* 沙僧
* 小白龙
*/