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操作

原文地址:https://www.cnblogs.com/wcss/p/12146199.html

时间: 2024-11-13 07:51:08

Foreach报错的相关文章

JSP标签c forEach报错(一)

1.jsp标签c:forEach报错,具体错误如下: 三月 31, 2014 9:31:14 下午 org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() for servlet [jsp] in context with path [/HighCharts] threw exception [Unable to compile class for JSP: An error occurred at l

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

今天发现了一个折磨我一天的问题: 在jsp文件中使用 <c:forEach items="${checkResult}" var="item"> </c:forEach> 一直报错: [ERROR] 2013-12-09 15:03:20,740 method:org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:253) Servlet.

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

一.背景 在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题 按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java.util.ConcurrentModificationException 但是下面这段代码实际情况却没报异常,是什么情况? 1 List<String> list = new ArrayList<String>(); 2 list.add("1"); 3 list.

filebeat+kafka+SparkStreaming程序报错及解决办法

17/07/01 03:07:21 WARN RandomBlockReplicationPolicy: Expecting 1 replicas with only 0 peer/s. 17/07/01 03:07:21 WARN BlockManager: Block input-0-1498849640800 replicated to only 0 peer(s) instead of 1 peers 17/07/01 03:07:26 ERROR Executor: Exception

MyBatis批量操作报错:Parameter &#39;xxxList&#39; not found. Available parameters are [list]

需求: 根据传入的参数批量 删除数据: DAO: List ll = new ArrayList<Integer>(); for(int i=10;i<25;i++){ ll.add(i); } int res = userMapper.delUser(li); System.out.println(res); xml: <delete id="delUser" parameterType="list" > delete from us

[mysqldumpslow 报错] Died at /usr/local/mysql/bin/mysqldumpslow line 161, &lt;&gt; chunk 236.

mysqldumpslow报错:Died at /usr/local/mysql/bin/mysqldumpslow line 161, <> chunk 236. 总结:是因为top数目太多了,mysqldumpslow遍历不过来的缘故. /usr/local/mysql/bin/mysqldumpslow -s -t 15 /root/db01-102-slow.log 1,把r去掉试试,还是报错,参数不识别. [[email protected] ]# /usr/local/mysql/

C# Directory.GetFiles()获取文件时如果是根目录时有隐藏文件则报错的处理

如果Directory.GetFiles("d:\"),则由于回收站是隐藏文件而报错,怎么躲避这种错误呢, 我要了一种办法,只要遇到隐藏文件夹就跳过的方法: foreach (var item in Directory.GetDirectories("d:")) { if ((new FileInfo(item).Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) //必须进行与运算,因

从async await 报错Unexpected identifier 谈谈对上下文的理解

原文首发地址:http://www.cnblogs.com/lonhon/p/7518231.html 先简单介绍下async await: async/await是ES6推出的异步处理方案,目的也很明确:更好的实现异步编程.   详细见阮大神 ES6入门 现在说说实践中遇到的问题:使用await报错Unexpected identifier 先上代码: var sleep = function (time) { return new Promise(function (resolve, rej

ecshop发票不能使用出现flow.php on line 723等报错

最模板给客户ecshop网站做编码转换出现个问题,网站在点结算页面出现Warning: Invalid argument supplied for foreach flow.php on line 723报错 找到723行代码是: foreach ($_CFG['invoice_type']['type'] as $key => $type) { if (!empty($type)) { $inv_type_list[$type] = $type . ' [' . floatval($_CFG[