为什么在foreach循环中不要对元素进行remove/add操作?

目录

  • 前言
  • 正文
    • 为什么结果如此不同?
    • remove/add方法?
    • 如何解决?

前言

在阿里巴巴Java开发手册中,有下面这样的规定:

这篇文章我们就来深入探讨其中的原因。

正文

为什么结果如此不同?

我们先来看看前言中的反例会出现什么意料之外的结果:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

仅仅是remove的元素不同,为什么会出现如此不同的结果呢?我们反编译上面报错的字节码文件可得:


import java.io.PrintStream;
import java.util.*;

public class Test
{

    public Test()
    {
    }

    public static void main(String args[])
    {
        ArrayList arraylist = new ArrayList();
        arraylist.add("1");
        arraylist.add("2");
        Iterator iterator = arraylist.iterator();
        do
        {
            if(!iterator.hasNext())
                break;        // 1
            String s = (String)iterator.next();         // 2
            if("2".equals(s))
                arraylist.remove(s);
        } while(true);
        System.out.println((new StringBuilder()).append("list:").append(arraylist).toString());
    }
}

通过这个反编译结果我们可以看到foreach底层其实还是使用iterator进行迭代。并且Debug上面的代码,发现当删除"2"元素后,代码执行到2处时报错;但当删除"1"元素后,代码会执行1处代码退出循环,由于没有执行2处的代码,所以删除"1元素"时不会报错。那么有人可能就会问了:为什么删除"2"元素后,1处代码不执行?我们可以通过查看ArrayList的hasNext()的源码找到答案:


class ArrayList {
    private int size;   // The size of the ArrayList (the number of elements it contains).

    private class Itr implements Iterator {
        int cursor;       // index of next element to return

	public boolean hasNext() {
	    return cursor != size;
	}
    }

}

当删除"1"元素后,cursor值为1("2"元素的下标),size值也为1,两者相等,故hasNext()返回false,所以执行1处代码;但当删除"2"元素后,cursor值为0("1"元素的下标),size值还是为1,两者不相等,故hasNext()返回true,所以无法执行1处代码。故而就出现了上面截然不同的结果。

remove/add方法?

那么为什么在执行2处代码时会报错呢?通过上面的报错信息我们可以看出,ConcurrentModificationException是在执行checkForComodification()的过程抛出的,checkForComodification()的源码如下所示:


final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

上面方法中的modCount是ArrayList中的一个成员变量,它表示该集合实际被修改的次数;expectedModCount是ArrayList中的一个内部类Itr(Itr是一个Iterator的实现:使用ArrayList.iterator()获取到的迭代器就是Itr类的实例)中的成员变量,它表示这个迭代器期望该集合被修改的次数,需要注意的是这个值是在集合调用iterator()时初始化,并且只有通过该迭代器对集合进行操作时,该值才会发生改变。

那么remove()/add()为什么会导致这两者的值不等呢?它对集合中的元素是怎样进行操作的呢?查看remove方法的源码:


public E remove(int index) {
    rangeCheck(index);
    checkForComodification();
    E result = parent.remove(parentOffset + index);
    this.modCount = parent.modCount;
    this.size--;
    return result;
}

由上面的源代码我们可以看到remove()仅对modCount变量进行了操作。于是我们就可以知道:在foreach(即iterator)对集合进行遍历时,元素在"自己"不知不觉的情况下被删除/添加,这时就会抛出异常,提示用户可能发生了并发修改。

如何解决?

明白了为什么之后,我们就需要思考如何去解决它:

  • 使用Iterator提供的remove()。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");

        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            if("2".equals(iterator.next())) {
                iterator.remove();
            }
        }

        System.out.println("list:" + list);
    }
}

  • 如果是List集合,还可以使用listIterator提供的remove()和add()。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");

        Iterator iterator = list.listIterator();
        while(iterator.hasNext()) {
            if("2".equals(iterator.next())) {
                iterator.remove();
//                ((ListIterator) iterator).add("3");
            }
        }

        System.out.println("list:" + list);
    }
}

  • 使用Java8提供的filter进行过滤:在Java8中可以把集合转换成流,并且对于流有一种filter操作,它可以对原始Stream进行某项过滤,通过过滤的元素被留下来生成一个新Stream。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>() {{
            add("1");
            add("2");
        }};

        list = list.stream().filter(name -> ! name.equals("2")).collect(Collectors.toList());
        System.out.println("list:" + list);
    }
}

原文地址:https://www.cnblogs.com/syhyfh/p/12551092.html

时间: 2024-10-13 00:06:11

为什么在foreach循环中不要对元素进行remove/add操作?的相关文章

不要在foreach循环里进行元素的remove/add操作

不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式. 反例: public class ForeachTest { private List<String> list = new ArrayList<String>(); @Test public void forTest() { list.add("1"); list.add("2"); for(String temp :

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

在阿里巴巴Java开发手册中,有这样一条规定:但是手册中并没有给出具体原因,本文就来深入分析一下该规定背后的思考.1.foreach循环foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素.Java语言从JDK 1.5.0开始引入foreach循环.在遍历数组.集合方面,foreach为开发人员提供了极大的方便.通常也被称之为增强for循环.foreach 语法格式如下: for(元素类型t 元素变量x : 遍历对象obj){ 引

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

这个问题我在实际写代码中也遇到过,当时还很疑惑,刚看到这里有一些解释得挺清楚,记录一下: 原文地址为:https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650123395&idx=1&sn=b089eac4f561f58ee8a92602db17f577&chksm=f36bb1a2c41c38b4f36c1a84f205e188a6a8aa2684f316e4dcb8d1162b6e94b970c670b2e5b

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

C#不允许在foreach循环中改变数组或集合中元素的值(注:成员的值不受影响),如以下代码将无法通过编译. foreach (int x in myArray) { x++; //错误代码,因为改变了元素的值 Console.WriteLine(x); } 如果要让自定义的数据类型支持foreach循环,则该类型必须实现IEnumerable<T>接口,且存在对应此列表的IEnumerator<T>实现. 实际上,在.Net的底层(IL语言层面)而言, foreach (var

在for、foreach循环体中添加数组元素

在开发工作中遇到这样一种情形,需要在循环体中动态向遍历中的数组添加元素并在当前循环遍历中使用数组的全部元素. 首先使用foreach循环来遍历数组,将代码简化抽象如下: $arr = array(1, 2, 3, 4, 5, 6, 7, 8); foreach($arr as $v) { if($v % 2 == 0) $arr[] = 11; echo $v . ' '; } 结果只打印出数组原来已有元素,在循环体中动态添加的元素并没有打印出来. 后来改用for循环,coding如下: $ar

12 在Foreach循环中如何获得当前迭代的索引

static void ForEachIndex() { int curIndex = 0; Dictionary<string, string> diction = new Dictionary<string,string>(); int counter = 0; diction.Add("One", "One"); diction.Add("Two", "Two"); diction.Add(&qu

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

C#在foreach循环中修改字典等集合出错:System.InvalidOperationException: Collection was modified; enumeration operation may not execute.这是因为在foreach中不允许修改集合,可通过如下方式修改dictPublish的值,如: Dictionary<string, string> _dict = new Dictionary<string, string>(dictPublis

在foreach循环中使用remove报ConcurrentModificationException异常原因

在foreach循环中使用remove报ConcurrentModificationException异常原因 我的代码具体是这样的 int dindex=0; int did=getInt("请输入需要删除的学生学号:"); for (Student student:list) { if(student.id==did){ list.remove(student); } } 这样会导致remove后,导致list在循环中下标和实际已经被修改后的下标不一致 我自己的解决方案是: int

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

前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in JS 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍 自弃者扶不起,自强者击不倒. 正文 你觉得你真的学会用forEach了么? 这是我之前对forEach循环的理解:就是一个普通语义化之后的for循环,可以被break,continue,return. 这篇文章将向你展示for