foreach迭代ArrayList时,真的不能删除元素吗?

ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况。这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.ConcurrentModificationException异常。比如下面的代码就会抛这个异常:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

List<String> list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

list.add("5");

 

for (String item: list) {

    if (item.equals("3")) {

        list.remove(item);

    }

}

System.out.println(Arrays.toString(list.toArray()));

那是不是在foreach循环时删除元素一定会抛这个异常呢?答案是否定的。

见这个代码:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

List<String> list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

list.add("5");

 

for (String item: list) {

    if (string.equals("4")) {

        list.remove(item);

    }

}

System.out.println(Arrays.toString(list.toArray()));

这段代码和上面的代码只是把要删除的元素的索引换成了4,这个代码就不会抛异常。为什么呢?

接下来先就这个代码做几个实验,把要删除的元素的索引号依次从1到5都试一遍,发现,除了删除4之外,删除其他元素都会抛异常。接着把list的元素个数增加到7试试,这时候可以发现规律是,只有删除倒数第二个元素的时候不会抛出异常,删除其他元素都会抛出异常。

好吧,规律知道了,可以从代码的角度来揭开谜底了。

首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常

其实,每次foreach迭代的时候都有两步操作():

  1. iterator.hasNext()  //判断是否有下个元素
  2. item = iterator.next()  //下个元素是什么,并赋值给上面例子中的item变量

next()方法的代码如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@SuppressWarnings("unchecked")

public E next() {

    checkForComodification();

    int i = cursor;

    if (i >= size)

        throw new NoSuchElementException();

    Object[] elementData = ArrayList.this.elementData;

    if (i >= elementData.length)

        throw new ConcurrentModificationException();

    cursor = i + 1;

    return (E) elementData[lastRet = i];

}

 

final void checkForComodification() {

    if (modCount != expectedModCount)

        throw new ConcurrentModificationException();

}

这时候你会发现这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount

  • modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会增加;
  • expectedModCount是Iterator类中特有的变量,指现在期望这个list被修改的次数是多少次,这个值在调用list.iterator()创建iterator的时候初始化为modCount,该值在iterator初始化直到使用结束期间不会改变。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时修改expectedModCount,这样就导致下次取值时检查到两个count不相等,从而抛出异常。

解决这个问题的一种方式是使用Iterator来操作列表:

?


1

2

3

4

5

6

Iterator<String> it = list.iterator();

while(it.hasNext()) {

    if (it.next().equals("3")) {

        it.remove();

    }

}

那么为什么这种方式不会抛出该异常呢?下面是ArrayList中内部类Itr的remove方法:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

public void remove() {

    if (lastRet < 0)

        throw new IllegalStateException();

    checkForComodification();

 

    try {

        ArrayList.this.remove(lastRet);

        cursor = lastRet;

        lastRet = -1;

        expectedModCount = modCount;

    catch (IndexOutOfBoundsException ex) {

        throw new ConcurrentModificationException();

    }

}

注意下面这句:

?


1

expectedModCount = modCount;

可以看出,在使用iterator()方法得到的Iterator对象后,通过iterator.remove方法是可以正确删除列表元素的,因为它保证了expectedModCount=modCount。

避免这个问题的另一种方法,是不使用foreach语句的for循环:

?


1

2

3

4

5

6

7

8

for (int i = 0; i < list.size(); ) {

    String s = list.get(i);

    if (s.equals("3")) {

        list.remove(i);

        continue;

    }

    i++;

}

回到问题上来,在使用foreach迭代ArrayList时,是可以删除任何一个元素的,且只能删除一个,而且这只能发生在迭代到倒数第二个元素的时候。比如下面的代码不会有异常:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

List<String> list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

list.add("5");

 

for (String item: list) {

    if (string.equals("4")) {

        list.remove("5"); //删除的不一定是当前元素

    }

}

System.out.println(Arrays.toString(list.toArray()));

其真正的原因是remove("5")这一句之后,下一次foreach语句将调用iterator.hasNext()方法,如果此时返回false,这样就不会进到next()方法里了,也就不会调用checkForComodification而导致异常了。

疑问:当循环到倒数第二个元素时,如果再多删除一个会怎样呢?比如:

?


1

2

3

4

5

6

7

for (String item: list) {

    if (item.equals("4")) {

        list.remove("1");

        list.remove("5");

    }

    System.out.println(Arrays.toString(list.toArray()));

}

这段代码中,list是可以被打印出来的,因为list.remove()方法可以正确执行,其结果也是正确的。但是执行完这次打印,进入下一次迭代时,又产生了checkForComodification异常,还没想明白为什么。如果哪位大牛知道,请留言。

时间: 2024-08-04 03:33:55

foreach迭代ArrayList时,真的不能删除元素吗?的相关文章

Java 遍历Map时 删除元素

package net.nie.test; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HashMapTest { private static Map<Integer, String> map=new HashMap<Integer,String>(); /** 1.HashMap 类映射不保证顺序:某些映射可明确保证其顺序: TreeMap 类 *

javascript-js删除元素时,页面会回到顶部

问题描述 js删除元素时,页面会回到顶部 js在删除元素时:$("ul li[id="1"]").remove(); 页面老是蹦跶,就是回到顶部,有什么办法让页面不动吗? 求教 解决方案 你的连接要return false阻止默认的href="#"操作,要不就去掉href="#"或者改为href="javascript:void(0)" <a href="#" onclick=&q

解决Python 遍历字典时删除元素报异常的问题_python

错误的代码① d = {'a':1, 'b':0, 'c':1, 'd':0} for key, val in d.items(): del(d[k]) 错误的代码② -- 对于Python3 d = {'a':1, 'b':0, 'c':1, 'd':0} for key, val in d.keys(): del(d[k]) 正确的代码 d = {'a':1, 'b':0, 'c':1, 'd':0} keys = list(d.keys()) for key, val in keys: d

asp.net C#数组遍历、排序、删除元素、插入、随机元素

asp教程.net c#数组遍历.排序.删除元素.插入.随机元素 数组遍历 short[] sts={0,1,100,200}; for(int i=0;i<sts.lenght;i++) {   if(sts[i]>50)  {   .....   } } 数组随机元素 public  hashtable  noorder(int count)         {             arraylist mylist = new arraylist();             hash

Java如何在List或Map遍历过程中删除元素_java

遍历删除List或Map中的元素有很多种方法,当运用不当的时候就会产生问题.下面通过这篇文章来再学习学习吧. 一.List遍历过程中删除元素 使用索引下标遍历的方式 示例:删除列表中的2 public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(2); list.add(3); list.add(

如何在遍历中使用iterator及reverse_iterator删除元素

众所周知,在使用迭代器遍历 STL 容器时,需要特别留意是否在循环中修改了迭代器而导致迭代器失效的情形.下面我来总结一下在对各种容器进行正向和反向遍历过程中删除元素时,正确更新迭代器的用法.本文完整源码:https://code.csdn.net/snippets/173595 首先,要明白使用正向迭代器(iterator)进行反向遍历是错误的用法,要不干嘛要有反向迭代器呢(reverse_iterator).其次,根据容器的特性,遍历删除操作的用法可以分为两组,第一组是 list 和 vect

对象-关于Java的ArrayList数据插入,删除等操作

问题描述 关于Java的ArrayList数据插入,删除等操作 import java.util.*; public class TestArrayList { public static void main(String []args) { ArrayList al=new ArrayList(); System.out.println("数组列表对象al的大小:"+al.size()); al.add("何"); al.add("叶"); a

js实现点击删除按钮,然后删除元素和按钮

问题描述 js实现点击删除按钮,然后删除元素和按钮 代码如下: 1 2 3 4 我想实现当我点击删除按钮时,当前的<li>元素和删除按钮也删除掉,removeMsg()方法该怎么写 解决方案 定义一个a标签 $('#fu a').click(// a标签单击事件处理函数 function(){ // 删除li标签 $('#fu li').remove(); } ); 解决方案二: 1 相关文章 javascript-用js循环添加元素时出现无论点击哪个后面的删除按钮都只能删掉最后一排且只能删除

JavaScript实现添加、查找、删除元素

  这篇文章主要汇总介绍了JavaScript实现添加.查找.删除元素的方法,十分的简单实用,有需要的小伙伴可以参考下. 代码很简单,这里就不多废话了. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60