集合在迭代过程中能否添加、删除或修改元素
2017-06-29 01:41
323 查看
1.使用 for 循环
2.使用 foreach 遍历
3.使用 Iterator 迭代器
在第一种情况下编译和运行都是可以的,第二种和第三种则会抛出 java.util.ConcurrentModificationException 的异常,这是为什么呢?
下面一段是来自百度知道的解释:
逻辑上讲,迭代时可以添加元素,但是一旦开放这个功能,很有可能造成很多意想不到的情况。
比如你在迭代一个 ArrayList,迭代器的工作方式是依次返回给你第0个元素,第1个元素,等等,假设当你迭代到第5个元素的时候,你突然在ArrayList的头部插入了一个元素,使得你所有的元素都往后移动,于是你当前访问的第5个元素就会被重复访问。
java 认为在迭代过程中,容器应当保持不变。因此,java 容器中通常保留了一个域称为 modCount,每次你对容器修改,这个值就会加1。当你调用 iterator 方法时,返回的迭代器会记住当前的 modCount,随后迭代过程中会检查这个值,一旦发现这个值发生变化,就说明你对容器做了修改,就会抛异常。
我们先看第三种情况,即使用 Iterator 迭代器对集合进行遍历,我们以 AbstractList 为例。
首先来看一下AbstractList是如何创建Iterator的,AbstractList有一个内部类:
而创建Iterator需要调用iterator()方法:
所以在调用集合的iterator方法之后实际上返回了一个内部类的实例。
我们看一下Itr这个类的next()方法是如何实现的:
checkForComodification()方法的实现如下:
modCount表示集合的元素被修改的次数,每次增加或删除一个元素的时候,modCount都会加一,而expectedModCount用于记录在集合遍历之前的modCount,检查这两者是否相等就是为了检查集合在迭代遍历的过程中有没有被修改,如果被修改了,就会在运行时抛出ConcurrentModificationException这个RuntimeException,以提醒开发者集合已经被修改。
这就说明了为什么集合在使用Iterator进行遍历的时候不能使用集合本身的add或者remove方法来增减元素。但是使用Iterator的remove方法是可以的,至于原因可以看一下这个方法的实现:
可以看到,在集合的元素被remove之后,expectedModCount被重新赋值,是的modCount总是等于expectedModCount,所以不会抛出ConcurrentModificationException异常。
而上面提到的第二种使用foreach来对集合进行遍历本质上和第三种情况是一样的,因为根据Oracle提供的文档,foreach内部的实现机制其实就是使用的Iterator。
第一种使用for循环进行遍历时内部使用的就是集合本身的遍历方法,这里不做讨论。
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for (int i = 0; i < list.size(); i++) { System.out.println(list.size()); if ("1".equals(list.get(i))){ list.add("4"); list.remove("1"); } }
2.使用 foreach 遍历
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for (String s : list){ if ("1".equals(s)){ list.add("4"); list.remove("1"); } }
3.使用 Iterator 迭代器
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if ("1".equals(iterator.next())) { iterator.remove(); list.add("4"); list.remove("1"); } }
在第一种情况下编译和运行都是可以的,第二种和第三种则会抛出 java.util.ConcurrentModificationException 的异常,这是为什么呢?
下面一段是来自百度知道的解释:
逻辑上讲,迭代时可以添加元素,但是一旦开放这个功能,很有可能造成很多意想不到的情况。
比如你在迭代一个 ArrayList,迭代器的工作方式是依次返回给你第0个元素,第1个元素,等等,假设当你迭代到第5个元素的时候,你突然在ArrayList的头部插入了一个元素,使得你所有的元素都往后移动,于是你当前访问的第5个元素就会被重复访问。
java 认为在迭代过程中,容器应当保持不变。因此,java 容器中通常保留了一个域称为 modCount,每次你对容器修改,这个值就会加1。当你调用 iterator 方法时,返回的迭代器会记住当前的 modCount,随后迭代过程中会检查这个值,一旦发现这个值发生变化,就说明你对容器做了修改,就会抛异常。
我们先看第三种情况,即使用 Iterator 迭代器对集合进行遍历,我们以 AbstractList 为例。
首先来看一下AbstractList是如何创建Iterator的,AbstractList有一个内部类:
private class Itr implements Iterator<E> { ... }
而创建Iterator需要调用iterator()方法:
public Iterator<E> iterator() { return new Itr(); }
所以在调用集合的iterator方法之后实际上返回了一个内部类的实例。
我们看一下Itr这个类的next()方法是如何实现的:
public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
checkForComodification()方法的实现如下:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
modCount表示集合的元素被修改的次数,每次增加或删除一个元素的时候,modCount都会加一,而expectedModCount用于记录在集合遍历之前的modCount,检查这两者是否相等就是为了检查集合在迭代遍历的过程中有没有被修改,如果被修改了,就会在运行时抛出ConcurrentModificationException这个RuntimeException,以提醒开发者集合已经被修改。
这就说明了为什么集合在使用Iterator进行遍历的时候不能使用集合本身的add或者remove方法来增减元素。但是使用Iterator的remove方法是可以的,至于原因可以看一下这个方法的实现:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } }
可以看到,在集合的元素被remove之后,expectedModCount被重新赋值,是的modCount总是等于expectedModCount,所以不会抛出ConcurrentModificationException异常。
而上面提到的第二种使用foreach来对集合进行遍历本质上和第三种情况是一样的,因为根据Oracle提供的文档,foreach内部的实现机制其实就是使用的Iterator。
第一种使用for循环进行遍历时内部使用的就是集合本身的遍历方法,这里不做讨论。
相关文章推荐
- 为什么java中迭代过程中不可以不可以添加或删除元素
- 在list集合中的添加、修改、删除和遍历元素
- python元素的添加、修改与删除
- Java使用迭代器遍历集合,遍历过程中可删除元素
- 删除正在循环迭代的集合元素的分析
- Java中List迭代过程中删除、新增元素的处理
- java 集合类如何正确在迭代中添加删除元素
- java.util.ConcurrentModificationException的解决办法 大家应该都知道, 在java中, 在对一些集合迭代的过程中对集合进行一些修改的操作, 比如说add,re
- STL容器迭代过程中删除元素技巧(转)
- 关于迭代集合同时删除元素报错的问题java.util.ConcurrentModificationException
- javascript添加、删除、修改等元素
- STL容器迭代过程中删除元素技巧
- 使用 SQL Server 添加删除修改查询储存过程
- js 对html 元素操作,添加删除修改
- 在shuiguo.xml文件中第三个水果中的苹果节点中添加节点<小苹果>small apple</小苹果>,添加小苹果后将其文本内容修改为“小苹果”,修改该节点后将其父节点苹果(即第三个苹果元素)删除
- C# 添加,修改,删除文件夹/文件集合
- STL容器迭代过程中删除元素技巧(转)
- NSDictionary的使用及常用方法(如实始化、添加元素、删除元素、修改元素值等)
- 刷新SQL Server所有视图、函数、存储过程 更多 sql 此脚本用于在删除或添加字段时刷新相关视图,并检查视图、函数、存储过程有效性。 [SQL]代码 --视图、存储过程、函数名称 DECLARE @NAME NVARCHAR(255); --局部游标 DECLARE @CUR CURSOR --自动修改未上状态为旷课 SET @CUR=CURSOR SCROLL DYNAMIC FO
- MongoDB常用命令汇总之修改、添加、删除集合数据。