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. JUC-线程间通信

    面试题: 两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z, 要求用线程间通信 线程间通信:1.生产者+消费者2.通知等待唤醒机制 多线程编程模版中 1.判断 ...

  2. mybatis(四):执行流程

    实现流程 // 读取mybatis-config.xml文件 InputStream inputStream = Resources.getResourceAsStream("mybatis ...

  3. ASP.NET Identity-验证与授权及管道事件

    https://www.cnblogs.com/OceanEyes/p/thinking-in-asp-net-mvc-apply-asp-net-identity-authentication.ht ...

  4. Codeforces Round #601 (Div. 2) C League of Leesins

    把每一次输入的一组数字存下来,然后把每个数字出现的组数存下来 然后找只出现过一次的数字a,那么这个数字a不是开头就是结尾,默认为开头(是哪个都无所谓),然后去找和它出现在同一组的两个数字b和c,而b和 ...

  5. 解决报错:ERROR 1005 (HY000): Can't create table 'market.orders' (errno: 150)

    1.描述问题: 在这里我新建了两张表(customers_info和orders) 表一:customers_info CREATE TABLE customers_info ( c_num INT( ...

  6. Python之路Day05

    字典 字典 -- dict Python的数据结构之一 字典是可变数据类型,无序的 定义 dic = {'key':'Value'} 键值对 字典的作用 存储数据,大大量的,将数据和数据起到关联作用 ...

  7. Git-免密提交

    全局设置git免密提交,打开git-bash输入命令: git config credential.helper store --global 单独对某个项目仓库设置时不加  --global 设置之 ...

  8. 创建本地yum源仓库

    更新本地yum源 yum仓库服务端配置如下 : 1. 创建yum仓库目录 mkdir -p /data/yum_data/ cd /data/yum_data/ #可以上传rpm包到此目录,此目录下面 ...

  9. spring自动装配和通过java实现装配

    1.组建扫描 在类上添加注解@Component注解可以实现组建扫描 @Component public class A{ ... } 2.自动装配 通过在属性上或者方法上添加@Autowired注解 ...

  10. Java 浮点数精度控制

    1.String.format​(String format,Object… args) Java中用String.format()来控制输出精度, format参数用来设置精度格式, args参数代 ...