Foreach报错
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报错的更多相关文章
- JSP标签c:forEach报错(一)
1.jsp标签c:forEach报错,具体错误如下: 三月 31, 2014 9:31:14 下午 org.apache.catalina.core.StandardWrapperValve invo ...
- mybatis foreach报错It was either not specified and/or could not be found for the javaType Type handler
或许是惯性思维,在mybatis使用foreach循环调用的时候,很多时候都是传一个对象,传一个List的情况很少,所以写代码有时候会不注意就用惯性思维方法做了. 今天向sql传参,传了一个List作 ...
- 【JSP jstl c标签】使用c:foreach 报错(警告)”test does not support runtime expressions“
后台封装的数据是个list,传递给前台,显示如下: <c:forEach items="${userInfo}" var="user"> 用户Nam ...
- jsp使用c:forEach报错 javax.servlet.jsp.PageContext.getELContext()Ljavax/el/ELContext的问题
今天发现了一个折磨我一天的问题: 在jsp文件中使用 <c:forEach items="${checkResult}" var="item"> & ...
- 视图中使用foreach报错问题
问题情境:thinkphp3.2版本,使用四层<foreach></foreach>循环变量时,报错以下错误: syntax error, unexpected 'endfor ...
- JSP标签c:forEach报错(二)
1.今天,我在用c标签写一些样例,结果出现一些错误,写下作为记录 具体错误如下: 三月 31, 2014 9:46:28 下午 org.apache.catalina.core.StandardWra ...
- [No000086]C#foreach集合被改变,报错处理方案
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; ...
- ArrayList在foreach正常迭代删除不报错的原因
一.背景 在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题 按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java. ...
- mybatis问题。foreach循环遍历数组报错情况,及其解决方法
根据条件查询数据列表,mybatis查询代码如下 如果只查询属于特定部门拥有的数据权限.这需要用 String[ ] codes保存当前部门及其子部门的部门编码. 所以需要在mybatis中遍历编码数 ...
随机推荐
- JUC-线程间通信
面试题: 两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z, 要求用线程间通信 线程间通信:1.生产者+消费者2.通知等待唤醒机制 多线程编程模版中 1.判断 ...
- fastadmin 框架中图片点击放大
fastadmin的原生图片预览,重新打开一个窗口太麻烦,使用layui做一个弹窗式的图片预览 1.将下面代码放在backend-init.js文件中 $('body').on('click', '[ ...
- laydate 限制结束日期不能大于起始日期
时间选择器在选择的时候,同时配置了另一个时间选择器内的参数 <div class="form-group"> <label for="exampleIn ...
- Hibernate项目的基本步骤和一些错误提示
以数据库中有一张user表为例: 1.编写POJO持久化类User.javaPOJO(Plain Old Java Objects),简单的Java对象.一个POJO类不用继承任何类,也无须实现任何接 ...
- vs2019 scanf 解决 C4996问题
1. 首先选择项目 2. 然后选择最下面那行的 工程属性, 其后于此处 3. 添加上 :_CRT_SECURE_NO_WARNINGS 最后保存,使用 scanf 读取即无报错了
- UNICODE下CString转string
真搞不懂,为毛C++这么多类型转换.. CString m_str(_T("fuck conversion")); char *chr=new char[m_str.GetLeng ...
- 2.11 webdriver中使用 FileUtils ()
http://snkcxy.iteye.com/blog/1845862 ex: 比较网页截图图片与预期是否一致 File screenshot=((TakesScreenshot)driver ). ...
- 阻塞队列BlockingQueue之ASynchronousQueue
一.SynchronousQueue简介 Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除 ...
- Go_MySQL查询插入删除
什么是预处理? 普通SQL语句执行过程: 客户端对SQL语句进行占位符替换得到完整的SQL语句. 客户端发送完整SQL语句到MySQL服务端 MySQL服务端执行完整的SQL语句并将结果返回给客户端. ...
- 2019年9月2日,亲自最新JDK12.0.2和JRE1.8.0的下载和配置(64bit),被其他教程坑了几圈忍不住自己写一篇
前因/ 新买了电脑,想在家写前端页面,看到最新版的JDK12.0.2和JRE1.8.0,就想着要用就用最新的,才对得起我的I9电脑.本人也是半生不熟,公司用的都是前辈配置好的环境,并且是JDK8很老了 ...