foreach循环中为什么不要进行remove/add操作
先来看一段代码,摘自阿里巴巴的java开发手册
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if("1".equals(temp)){
a.remove(temp);
}
}
此时执行代码,没有问题,但是需要注意,循环此时只执行了一次。具体过程后面去分析。再来看一段会出问题的代码:
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if("2".equals(temp)){
a.remove(temp);
}
}
输出为:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at luyudepackage.waitTest.main(waitTest.java:57)
是不是很奇怪?接下来将class文件,反编译下,结果如下
List a = new ArrayList();
a.add("1");
a.add("2");
Iterator i$ = a.iterator();
do
{
if(!i$.hasNext())
break;
String temp = (String)i$.next();
if("1".equals(temp))
a.remove(temp);
} while(true);
几个需要注意的点:
1.foreach遍历集合,实际上内部使用的是iterator。
2.代码先判断是否hasNext,然后再去调用next,这两个函数是引起问题的关键。
3.这里的remove还是list的remove方法。
先去观察下list.remove()方法中的核心方法fastRemove()方法。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
注意第二行,modCount++,此处先不表,下文再说这个参数。
顺路观察下list.add()方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
注意第二行的注释,说明这个方法也会使modCount++
再去观察下,iterator()方法
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
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//万恶之源
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
几个需要注意的点:
1.在iterator初始化的时候(也就是for循环开始处),expectedModCount = modCount,猜测是和当时list内部的元素数量有关系(已证实)。
2.当cursor != size的时候,hasNext返回true
3.next()函数的第一行,checkForComodification()这个函数就是报错的原因 这个函数就是万恶之源
4.第39行,mod != expectedModCount 就会抛出ConcurrentModificationException()
接下来分析文章开头的第一个例子,为啥不会报错?
第一个例子执行完第一次循环后,mod = 3 expectedModCount =2 cursor = 1 size = 1 所以程序在执行hasNext()的时候会返回false,所以程序不会报错。
第二个例子执行完第二次循环后,mod = 3 expectdModCount = 2 cursor = 2 size = 1 此时cursor != size 程序认定还有元素,继续执行循环,调用next方法但是此时mod != expectedModCount 所以此时会报错。
道理我们都懂了,再看一个例子
public static void main(String[] args) throws Exception {
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
System.out.println(temp);
if("2".equals(temp)){
a.add("3");
a.remove("2");
}
}
}
此时输出为:
1
2
显然,程序并没有执行第三次循环,第二次循环结束,cursor再一次等于size,程序退出循环。
与remove类似,将文章开头的代码中remove替换为add,我们会发现无论是第一个例子还是第二个例子,都会抛出ConcurrentModificationException错误。
原因同上,代码略。
手册上推荐的代码如下
Iterator<String> it = a.iterator(); while(it.hasNext()){
String temp = it.next(); if(删除元素的条件){
it.remove();
}
}
此时remove是iterator的remove,我们看一下它的源码:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet; //index of last element returned;-1 if no such
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
注意第10行,第8行,所以此时程序不会有之前的问题。
但是手册上推荐的方法,在多线程环境还是有可能出现问题,一个线程执行上面的代码,一个线程遍历迭代器中的元素,同样会抛出CocurrentModificationException。
如果要并发操作,需要对iterator对象加锁。
平时遍历list,然后删除某个元素的时候,如果仅仅删除第一个且删除之后调用break //代表着此时不会再去执行iterator.next方法 也就不会触发万恶之源
而如果要删除所有的某元素,则会报错,谨记!
Ps再来看一个佐证
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for(int i : list){
System.out.println(i);
if(i == 2){
list.remove((Object)2);
}
}
}
此时只会输出
1
2
当把remove对象改为3时候,再次报错。
foreach循环中为什么不要进行remove/add操作的更多相关文章
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁. 正例: Iterator&l ...
- 为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作--java.util.ConcurrentModificationException
摘要 foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素. 在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体 ...
- 为什么禁止在 foreach 循环里进行元素的 remove/add 操作
首先看下边一个例子,展示了正确的做法和错误的错发: 这是为什么呢,具体原因下面进行详细说明: 1.foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数 ...
- 为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?
在阅读<阿里巴巴Java开发手册>时,发现有一条关于在 foreach 循环里进行元素的 remove/add 操作的规约,具体内容如下: 错误演示 我们首先在 IDEA 中编写一个在 f ...
- 「译」forEach循环中你不知道的3件事
前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in ...
- C#在foreach循环中修改字典等集合出错的处理
C#在foreach循环中修改字典等集合出错:System.InvalidOperationException: Collection was modified; enumeration operat ...
- C#不允许在foreach循环中改变数组或集合中元素的值(注:成员的值不受影响)
C#不允许在foreach循环中改变数组或集合中元素的值(注:成员的值不受影响),如以下代码将无法通过编译. foreach (int x in myArray) { x++; //错误代码,因为改变 ...
- 16.1 foreach 循环中捕获变量的变化
在 foreach 循环内的匿名函数(通常为Lambda表达式)中捕获循环 变量时要格外小心.代码清单16-1就展示了这样一个简单的示例,它看上去似乎会输出 x . y . z . string[] ...
- 【PHP】 foreach循环中变量引用的一道面试题
$a = array('a','b','c'); foreach($a as &$v){} foreach($a as $v){ } var_dump($a); 现在.不要打开浏览器,猜测一下 ...
随机推荐
- weblogic 启动问题
通过startWebLogic.cmd 手动启动weblogic可以成功启动 myeclipse配置后,runserver 就会报 Error occurred during initializati ...
- Struts2-文件上传下载
Struts2文件上传 提供 FileUpload 拦截器,用于解析 multipart/form-data 编码格式请求,解析上传文件的内容 fileUpload拦截器 默认在 defaultSta ...
- ReactNative环境搭建扩展篇——安装后报错解决方案
之前一篇写了<逻辑性最强的React Native环境搭建与调试>说了RN的安装,今天在这里做一个复盘,让我们能够更直观更深入的了解React Native(以下简称RN),这一篇重点来说 ...
- 再起航,我的学习笔记之JavaScript设计模式16(享元模式)
### 享元模式 **享元模式(Flyweight):** 运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销. 上回我们在组合模式中创建了文章列表类,这次我们要向不同的文 ...
- C++学习日记(一)————类与对象
C++远征之封装篇(上)笔记 所有内容都是听课笔记,愿课堂视频如下: C++远征之封装篇(上)-慕课网http://www.imooc.com/learn/382 类和对象 1 什么是类,什么是对象 ...
- Linux-grep 命令和find 命令 (6)
grep与find区别: grep:查找指定目录下过滤文本文件中行数据 find:查找指定目录下文件 grep使用 格式: grep "text" * [选项] 选项: ...
- JDBC(一)之细说JDBC
Properties info = new Properties();//要参考数据库文档 info.setProperty("user", "root"); ...
- 正则语言引擎:一个简单LEX和YACC结合运用的实例
本文先描述了LEX与YACC的书写方法.然后利用LEX与YACC编写了一个简单正则语言的引擎(暂时不支持闭包与或运算),生成的中间语言为C语言. 正则引擎应直接生成NFA或DFA模拟器的输入文件,但在 ...
- React——state
在React--组件中介绍过组件有两种定义方式:函数形式以及类形式.但是要想在组件中使用state,就必须使用类形式定义组件. 组件中的state是组件私有的,完全由组件自己控制. 使用类形式定义一个 ...
- [转载]C header files matching your running
原文地址:C header files matching your running kernel were not found.作者:[Opser]小默 c header files matching ...