本文共 9439 字,大约阅读时间需要 31 分钟。
先来看下面的示例:
public class Demo { public static void main(String[] args) throws IOException { Listlist = new LinkedList<>(); list.add("唐僧"); list.add("孙悟空"); list.add("猪八戒"); list.add("沙僧"); list.add("小白龙"); ListIterator 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迭代器的实现类 */ private class ListItr implements ListIterator { // 全局变量,上一次执行 next() 或者 previos() 方法时的节点 private Node lastReturned; // 后继结点 private Node 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 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 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()方法时做了三件事:
注意上面Demo.java中的代码,当第六次调用next()时将抛出NoSuchElementException异常,从next()源码中我们可以查看它首先要判断是否还有下一个元素。
没有下一个元素则抛出该异常。
所以我们如果要遍历迭代器,应该如下:
while (iterator.hasNext()) { System.out.println(iterator.next());}
但在遍历时可能遇到一个问题,如果你想删除或增加元素怎么办,看下面的代码:
public class Demo { public static void main(String[] args) throws IOException { Listlist = new LinkedList<>(); list.add("唐僧"); list.add("孙悟空"); list.add("猪八戒"); list.add("沙僧"); list.add("小白龙"); ListIterator 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是怎么回事呢?
在上面代码中我们在迭代器中调用了LinkedList类的add()或remove()方法,只更新了modCount值,而迭代器中的expectedModCount没有更新,所以会抛出异常。
但如果调用ListIterator内部的remove()或add()方法就不会抛出此异常,因为同时更新了expectedModCount和modCount的值,如下源码能够证明:
使用的目的为了实现快速失败机制(fail-fast),在Java集合中有很多集合都实现了快速失败机制。
快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
看下面的例子就是多线程产生快速失败:
public class Demo { public static Listlist = 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 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 Listlist = new LinkedList<>(); public static void main(String[] args) throws IOException { list.add("唐僧"); list.add("孙悟空"); list.add("猪八戒"); list.add("沙僧"); list.add("小白龙"); ListIterator iterator = list.listIterator(); while (iterator.hasNext()) { String next = iterator.next(); if (next.equals("猪八戒")) { iterator.remove(); } } for (String s : list) { System.out.println(s); } }}/** * 打印结果: * 唐僧 * 孙悟空 * 沙僧 * 小白龙 */
参考连接:
转载地址:http://djkx.baihongyu.com/