先来看一段代码,摘自阿里巴巴的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操作的更多相关文章

  1. 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁

    不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁. 正例: Iterator&l ...

  2. 为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作--java.util.ConcurrentModificationException

    摘要 foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素. 在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体 ...

  3. 为什么禁止在 foreach 循环里进行元素的 remove/add 操作

    首先看下边一个例子,展示了正确的做法和错误的错发: 这是为什么呢,具体原因下面进行详细说明: 1.foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数 ...

  4. 为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?

    在阅读<阿里巴巴Java开发手册>时,发现有一条关于在 foreach 循环里进行元素的 remove/add 操作的规约,具体内容如下: 错误演示 我们首先在 IDEA 中编写一个在 f ...

  5. 「译」forEach循环中你不知道的3件事

    前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in ...

  6. C#在foreach循环中修改字典等集合出错的处理

    C#在foreach循环中修改字典等集合出错:System.InvalidOperationException: Collection was modified; enumeration operat ...

  7. C#不允许在foreach循环中改变数组或集合中元素的值(注:成员的值不受影响)

    C#不允许在foreach循环中改变数组或集合中元素的值(注:成员的值不受影响),如以下代码将无法通过编译. foreach (int x in myArray) { x++; //错误代码,因为改变 ...

  8. 16.1 foreach 循环中捕获变量的变化

    在 foreach 循环内的匿名函数(通常为Lambda表达式)中捕获循环 变量时要格外小心.代码清单16-1就展示了这样一个简单的示例,它看上去似乎会输出 x . y . z . string[] ...

  9. 【PHP】 foreach循环中变量引用的一道面试题

    $a = array('a','b','c'); foreach($a as &$v){} foreach($a as $v){ } var_dump($a); 现在.不要打开浏览器,猜测一下 ...

随机推荐

  1. 用PyCharm执行测试成功但无法生成HTMLTestRunner报告

    问题:代码写的没问题,执行也成功了,但就是无法生成HTMLTestRunner的报告 其实这是编辑器搞得鬼,编辑器为了方便用户执行测试,都有一项功能,可以用编辑器来调用unittest或者nose来执 ...

  2. monogodb使用

    菜鸟教程有相关介绍,已经很详细. http://www.runoob.com/mongodb/mongodb-databases-documents-collections.html 网上找了一些博客 ...

  3. leetcode 001 Two Sun

    Given an array of integers, return indices of the two numbers such that they add up to a specific ta ...

  4. github+hexo搭建自己的博客网站(七)注意事项(避免read.me,CNAME文件的覆盖,手动改github page的域名)

    详细的可以查看hexo博客的演示:https://saucxs.github.io/ 绑定域名可以查看:http://www.chengxinsong.cn 可以查看在github上生成的静态文件(如 ...

  5. log4j与log4j.properties的配置

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt233 摘要: 一.配置步骤 1.  在应用程序中使用log4j 2.  把l ...

  6. Java 类的热替换 —— 概念、设计与实现

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp71   Java 类的热替换 -- 概念.设计与实现 构建基于 Java ...

  7. 团队作业4--第一次项目冲刺(Alpha版本) 5

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 对前两天完成的功能进行整合 测试完成功能(测试算法是否有bug,界面设计是否人性化,适合用户使用.) 四.困难与问题 在对前两 ...

  8. 201521123122 《java程序设计》 第六周实验总结

    201521123122 <java程序设计>第六周实验总结 1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想 ...

  9. 201521123057 《Java程序设计》第2周学习总结

    1. 本章学习总结 本周Java教学主要围绕Java的基本语法展开.在本周的学习中,我了解到了: -Integer的取值范围 -if...else条件式,switch条件式,while循环,break ...

  10. 201521123037 《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. Java多线程之Executor.ExecutorService.Executors.Callable.Futur ...