在使用迭代器遍历集合时,为何不能使用集合的remove方法?
2018-01-08 21:56
447 查看
一、发现问题
在看到数据结构与算法分析第三章时,看到这样一段代码public static void removeEvens(List<Integer> list) { for (Integer x : list) { if (x % 2 == 0) { list.remove(x); } } }
这段代码想完成的功能很清楚,就是要移除list集合中所有的偶数,但是这段程序会报错,抛出
ConcurrentModificationException异常。这是为什么呢?书上是这样说的,编译器在看到一个实现了Interator接口的对象的增强for循环时,会自动地重写,变成使用迭代器来遍历集合。
上面for循环代码实际上会被翻译为:
Iterator<Integer> iterator=list.iterator(); while(iterator.hasNext()){ Integer element=iterator.next(); if(element%2==0){ list.remove(element); } }
问题就很清楚了,在使用迭代器遍历集合时,如果使用了集合的remove方法,则会抛出ConcurrentModificationException异常。这是为什么呢?书上只说了在使用Iterator时,如果对正在迭代的集合进行结构上的改变,即使用集合的add,remove,clear方法是,迭代器就不再合法。(不好理解)
二、解决问题
网上也有些写的挺好的博客,这里自己再进行整理一下,以加深理解。Iterator<Integer> iterator=list.iterator();
因为list是一个集合对象,它实现了Iterable接口,而该接口要求集合实现iterator的方法,返回值是一个Iterator类型。
public interface Iterator<AnyType>{ boolean hasNext(); AnyType next(); void remove();
在ArrayList中,iterator方法会返回一个Itr类型的对象。
public Iterator<E> iterator() { return new Itr(); }
//内部类 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 //修改次数--->用于检测在迭代期间被修改的情况,expectedModeCount初始值是modCount,通过expectedModeCount与modCount的比较来进行错误检测 int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { //错误检测 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); //elementData存在于外部类ArrayList中,它以数组形式存储着集合元素,通过外部类的隐式引用来使用elementData Object[] elementData = ArrayList.this.elementData; //如果此时的游标大于集合元素的长度 if (i >= elementData.length) throw new ConcurrentModificationException(); //游标+1 cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { //迭代器的remove方法,实际上要靠集合的remove方法来实现, //值的注意的是:它对游标进行了修改,并且对象expectedModCount进行了修正 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 & 4000 amp;& modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } //错误检测方法,通过比较modCount与expectedModCount是否一致,不一致则抛出异常 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
迭代器的remove方法与集合的remove方法,最大的不同是,迭代器的remove方法中包括对游标和expectedModCount的修正。
因为Iterator是在一个独立的线程中工作的,它在new Itr()进行初始化时,会记录当时集合中的元素,可以理解为记录了集合的状态,在使用集合的Remove方法对集合进行修改时,被记录的集合状态并不会与之同步改变,所以在cursor指向下一个要返回的元素时,可能会发生找不到的错误,即抛出ConcurrentModificationException异常。
很明显,如果使用迭代器提供的remove方法时,会对cursor进行修正,故不会出现错误,此外,还会修正expectedModCount,通过它来进行错误检测(迭代过程中,不允许集合的add,remove,clear等改变集合结构的操作)。
The world is such small,it’s like when you turn around,you don’t know who you will see. The world is so big,as if when you turn around,you never know who will disappear.
相关文章推荐
- 迭代(遍历)时候不可以使用集合的remove和add方法,但可使用Java迭代器的remove和add方法
- map集合的4种遍历方法和List集合的2种遍历和迭代器Iterator的使用
- 关于for each循环不能直接使用集合的remove方法的原因
- 使用迭代器时为什么不能用集合删除元素的方法
- ArrayList /Vector/LinkedList用/迭代器遍历 /列表迭代器遍历 /使用size()和get()方法遍历/使用增强for遍历集合
- 如何边遍历集合边删除元素--使用Iterator中的remove()方法
- 关于集合中迭代器中next()方法重复使用造成的错误
- JAVA使用增强for循环和迭代器遍历Map集合
- list,set等集合遍历时,不能remove集合中的元素。需要new一个Object或者list,set,里面add需要删除的元素,等集合遍历完了进行remove(Object)或者removeAll(list/set)操作
- C#使用yield关键字让自定义集合实现foreach遍历的方法
- Java学习之容器上(Collection接口常用方法,Iterator接口,使用foreach循环遍历Collection集合元素,Set集合通用知识(Hashset类,hashcode()与Lin
- 使用迭代器Iterator和增强for循环如何遍历集合
- Java学习之容器上(Collection接口常用方法,Iterator接口,使用foreach循环遍历Collection集合元素,Set集合通用知识(Hashset类,hashcode()与LinkedHashSet类))
- Iterator迭代器的使用,对for和while对集合遍历并排
- 集合遍历之迭代器实现方法
- Java使用迭代器遍历集合,遍历过程中可删除元素
- Static 方法里面为何不能使用this
- Java基础知识强化之集合框架笔记08:Collection集合自定义对象并遍历案例(使用迭代器)
- Java类集框架——Iterator和ListIterator 迭代器的使用(遍历集合)
- 黑马程序员---集合体系的继承关系图。顶层接口Collection中的方法,迭代器Iterator使用和原理,List派系特点