List<String> a = new ArrayList<String>();
2 a.add("1");
3 a.add("2");
4 for (String temp : a) {
5 if("1".equals(temp)){
6 a.remove(temp);
7 }
8 }

此时执行代码,没有问题,但是需要注意,循环此时只执行了一次。具体过程后面去分析。再来看一段会出问题的代码:

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文件,反编译下,结果如下

 1 List a = new ArrayList();
2 a.add("1");
3 a.add("2");
4 Iterator i$ = a.iterator();
5 do
6 {
7 if(!i$.hasNext())
8 break;
9 String temp = (String)i$.next();
10 if("1".equals(temp))
11 a.remove(temp);
12 } while(true);

几个需要注意的点:

1.foreach遍历集合,实际上内部使用的是iterator。

2.代码先判断是否hasNext,然后再去调用next,这两个函数是引起问题的关键。

3.这里的remove还是list的remove方法。

先去观察下list.remove()方法中的核心方法fastRemove()方法。

1 private void fastRemove(int index) {
2 modCount++;
3 int numMoved = size - index - 1;
4 if (numMoved > 0)
5 System.arraycopy(elementData, index+1, elementData, index,
6 numMoved);
7 elementData[--size] = null; // clear to let GC do its work
8 }

注意第二行,modCount++,此处先不表,下文再说这个参数。

顺路观察下list.add()方法

1 public boolean add(E e) {
2 ensureCapacityInternal(size + 1); // Increments modCount!!
3 elementData[size++] = e;
4 return true;
5 }

注意第二行的注释,说明这个方法也会使modCount++

再去观察下,iterator()方法

1 public Iterator<E> iterator() {
2 return new Itr();
3 }
 1 private class Itr implements Iterator<E> {
2 int cursor; // index of next element to return
3 int lastRet = -1; // index of last element returned; -1 if no such
4 int expectedModCount = modCount;
5
6 public boolean hasNext() {
7 return cursor != size;
8 }
9
10 @SuppressWarnings("unchecked")
11 public E next() {
12 checkForComodification();//万恶之源
13 int i = cursor;
14 if (i >= size)
15 throw new NoSuchElementException();
16 Object[] elementData = ArrayList.this.elementData;
17 if (i >= elementData.length)
18 throw new ConcurrentModificationException();
19 cursor = i + 1;
20 return (E) elementData[lastRet = i];
21 }
22
23 public void remove() {
24 if (lastRet < 0)
25 throw new IllegalStateException();
26 checkForComodification();
27
28 try {
29 ArrayList.this.remove(lastRet);
30 cursor = lastRet;
31 lastRet = -1;
32 expectedModCount = modCount;
33 } catch (IndexOutOfBoundsException ex) {
34 throw new ConcurrentModificationException();
35 }
36 }
37
38 final void checkForComodification() {
39 if (modCount != expectedModCount)
40 throw new ConcurrentModificationException();
41 }
42 }

几个需要注意的点:

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 所以此时会报错。

道理我们都懂了,再看一个例子

 1 public static void main(String[] args) throws Exception {
2 List<String> a = new ArrayList<String>();
3 a.add("1");
4 a.add("2");
5 for (String temp : a) {
6 System.out.println(temp);
7 if("2".equals(temp)){
8 a.add("3");
9 a.remove("2");
10 }
11 }
12 }

此时输出为:

1

2

显然,程序并没有执行第三次循环,第二次循环结束,cursor再一次等于size,程序退出循环。

与remove类似,将文章开头的代码中remove替换为add,我们会发现无论是第一个例子还是第二个例子,都会抛出ConcurrentModificationException错误。

原因同上,代码略。


手册上推荐的代码如下

1 Iterator<String> it = a.iterator(); while(it.hasNext()){
2 String temp = it.next(); if(删除元素的条件){
3 it.remove();
4 }
5 }

此时remove是iterator的remove,我们看一下它的源码:

 1  public void remove() {
2 if (lastRet < 0)
3 throw new IllegalStateException();
4 checkForComodification();
5
6 try {
7 ArrayList.this.remove(lastRet);
8 cursor = lastRet; //index of last element returned;-1 if no such
9 lastRet = -1;
10 expectedModCount = modCount;
11 } catch (IndexOutOfBoundsException ex) {
12 throw new ConcurrentModificationException();
13 }
14 }

注意第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);
}
} } 只是ArrayList是线程不安全的,在被修改后再继续迭代就报错,
modCount是指ArrayList的修改次数,每次add或remove都会自增,
当迭代时,就是将这个modCount暂存在expectedModCount中,
每次获取下一个元素时,都检查下修改次数是否有变动,有变动则不再继续迭代,而是抛出错误ConcurrentModificationException
这样就强制要求在迭代时不能进行remove/add操作,而foreach会编译成迭代,所以foreach时也不能进行remove/add操作

Foreach报错的更多相关文章

  1. JSP标签c:forEach报错(一)

    1.jsp标签c:forEach报错,具体错误如下: 三月 31, 2014 9:31:14 下午 org.apache.catalina.core.StandardWrapperValve invo ...

  2. mybatis foreach报错It was either not specified and/or could not be found for the javaType Type handler

    或许是惯性思维,在mybatis使用foreach循环调用的时候,很多时候都是传一个对象,传一个List的情况很少,所以写代码有时候会不注意就用惯性思维方法做了. 今天向sql传参,传了一个List作 ...

  3. 【JSP jstl c标签】使用c:foreach 报错(警告)”test does not support runtime expressions“

    后台封装的数据是个list,传递给前台,显示如下: <c:forEach items="${userInfo}" var="user"> 用户Nam ...

  4. jsp使用c:forEach报错 javax.servlet.jsp.PageContext.getELContext()Ljavax/el/ELContext的问题

    今天发现了一个折磨我一天的问题: 在jsp文件中使用 <c:forEach items="${checkResult}" var="item"> & ...

  5. 视图中使用foreach报错问题

    问题情境:thinkphp3.2版本,使用四层<foreach></foreach>循环变量时,报错以下错误: syntax error, unexpected 'endfor ...

  6. JSP标签c:forEach报错(二)

    1.今天,我在用c标签写一些样例,结果出现一些错误,写下作为记录 具体错误如下: 三月 31, 2014 9:46:28 下午 org.apache.catalina.core.StandardWra ...

  7. [No000086]C#foreach集合被改变,报错处理方案

    using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; ...

  8. ArrayList在foreach正常迭代删除不报错的原因

    一.背景 在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题 按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java. ...

  9. mybatis问题。foreach循环遍历数组报错情况,及其解决方法

    根据条件查询数据列表,mybatis查询代码如下 如果只查询属于特定部门拥有的数据权限.这需要用 String[ ] codes保存当前部门及其子部门的部门编码. 所以需要在mybatis中遍历编码数 ...

随机推荐

  1. 831. KMP字符串(模板)

    给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字. 模板串P在模式串S中多次作为子串出现. 求出模板串P在模式串S中所有出现的位置的起始下标. 输入格式 第一行输入整 ...

  2. Codeforces 524C.The Art of Dealing with ATM(暴力)

    我先采用了智障解法(n * n枚举...刚开始把n看成1000了还以为能过) 理所当然的t了,不过我怀疑优化一下能过?(感觉数据不太行的亚子 然后就是O(n * k * k)的解法,看到好多人快乐二分 ...

  3. FactoryBean的作用

    Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean.FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该Facto ...

  4. 2019牛客多校第七场E Find the median 离散化+线段树维护区间段

    Find the median 题意 刚开始集合为空,有n次操作,每次操作往集合里面插入[L[i],R[i]]的值,问每次操作后中位数是多少 分析 由于n比较大,并且数可以达到1e9,我们无法通过权值 ...

  5. java简单学生成绩管理系统

    题目要求: 一. 数据结构要求:(5 分) 1.定义 ScoreInformation 类,其中包括七个私有变量(stunumber, name, mathematicsscore, englishi ...

  6. 如何在vivado中调用ultraedit 编辑器

    ISE下点击菜单Edit -> Preferences -> Editor. 在Editor选项框里选择Custom,在Command line syntax文本框里输入: {C:/Pro ...

  7. 【Unity|C#】基础篇(0)——C#与.NET框架

    [学习资料] <C#图解教程>(第1章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu.c ...

  8. sql server下载教程

    进入官网:https://www.microsoft.com/zh-cn/download/details.aspx?id=29066 点击下载即可: 安装教程:可去csdn下载: win10系统下安 ...

  9. spring(二):体系结构&核心模块

    Spring框架 帮助管理对象及其依赖关系 提供如通用日志记录.性能统计.安全控制.异常处理等面向切面的能力 帮助管理数据库事务,提供了一套简单的JDBC访问实现,提供与第三方数据访问框架集成(如Hi ...

  10. 2.9 log4j 在测试过程中打印执行日志

    Apache Log4j配置说明/log4j的分层打印    http://blog.csdn.net/zzy7075/article/details/53763728