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

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循环时删除元素一定会抛这个异常呢?答案是否定的。

见这个代码:

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()方法的代码如下:

@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来操作列表:

Iterator<String> it = list.iterator();
while(it.hasNext()) {if (it.next().equals("3")) {it.remove();}
}

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

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();}
}

注意下面这句:

expectedModCount = modCount;

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

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

for (int i = 0; i < list.size(); ) {String s = list.get(i);if (s.equals("3")) {list.remove(i);continue;}i++;
}

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

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而导致异常了。

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

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异常,还没想明白为什么。如果哪位大牛知道,请留言。

参考:

http://rongmayisheng.com/post/%E7%A0%B4%E9%99%A4%E8%BF%B7%E4%BF%A1java-util-arraylist%E5%9C%A8foreach%E5%BE%AA%E7%8E%AF%E9%81%8D%E5%8E%86%E6%97%B6%E5%8F%AF%E4%BB%A5%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0

http://stackoverflow.com/questions/11042552/what-does-the-modcount-variable-when-debugging-the-collection

from: https://my.oschina.net/itblog/blog/422649

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

  1. 写一段代码在遍历 ArrayList 时移除一个元素?

    今天楼主继续分享一道经典Java面试题并进行相关知识点的拓展: 上题: 写一段代码在遍历 ArrayList 时移除一个元素? 该问题的关键在于面试者使用的是 ArrayList 的 remove() ...

  2. 遍历ArrayList时移除重复元素失效问题

    遍历ArrayList时移除重复元素失效问题 在使用ArrayList时遇到个问题,例如: public static void remove(ArrayList<String> list ...

  3. Python遍历列表时其中的删除元素_CodingPark编程公园

    遍历列表时其中的删除元素 删除列表中的元素我们通常会用 .remove( ) 因为删除元素后,整个列表的元素会往前移动,而i却是在最初就已经确定了,是不断增大的,所以并不能得到想要的结果. 所以会造成 ...

  4. 通过foreach遍历ArrayList时同时修改报错分析

    遍历ArrayList可以有for循环.foreach.迭代器iterator.listIterator,其中通过foreach来遍历同时修改 ArrayList时会抛出 ConcurrentModi ...

  5. ArrayList中remove()方法删除元素之后下标重定位的问题

    需求: 有一个ArrayList数组,要求删除长度大于5的字符串,如:arr = {"ab1","123ad","bca","da ...

  6. foreach遍历ArrayList时的不当操作与解决

    1.不当操作1:ConcurrentModificationException并发修改异常 代码: public class TestList {public static void main(Str ...

  7. java利用循环打印AVA_ava.util.ArrayList在foreach循环遍历时可以删除元素

    ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况.这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.Concur ...

  8. ArrayList删除元素的细则

      删除ArrayList数组中某个元素,通常会使用for循环匹配目标元素完成删除操作. public void remove(List<String> list, String str) ...

  9. List遍历中删除元素

    List遍历主要有索引下标遍历.for循环遍历和Iterator迭代遍历,索引下标和for循环在遍历中删除元素都存在问题,Iterator迭代可以实现遍历中删除元素. 索引下标遍历 List<I ...

最新文章

  1. 彻底解决Webpack打包慢的问题
  2. 关于python2和python3除法的区别
  3. 监控调优工具详细参数整理
  4. Kafka万亿级消息实战解决方案干货
  5. Numpy学习笔记(三)
  6. apkg格式怎么打开_天正软件如何导成CAD识别格式
  7. 软件图标显示不正常的问题
  8. 分组背包+树形DP(BY LPX)
  9. openbsd mysql_使用OpenBSD一年的总结
  10. 香港美国CERA机房你怎么选择?
  11. Go开发 之 设计模式
  12. 平板插上显示无服务器,教你一招,让ipad变成免费的外置显示器!
  13. 美国大学计算机工程专业排名,2018美国大学计算机工程专业排名_美国大学计算机工程排名...
  14. response.reset() 与response.resetbuffer使用场景
  15. 最流行的微服务应用框架有哪些
  16. web开发—— 前端基础(6) ——语义化标签
  17. 婴幼儿蛋白质过敏怎么回事
  18. 车速与档位匹配关系_驾考科目三车速和档位如何匹配
  19. 计算机网络05局域网
  20. qt中mysql怎么支持事务_Qt踩坑之mysql数据库不支持事务操作?

热门文章

  1. DDNS 的工作原理及其在 Linux 上的实现--转
  2. Python数据结构与算法(第三天)
  3. 【模型迭代】模型迭代
  4. 移动金融业务风控框架及设备风险识别的意义(下)
  5. 诚安聚立总裁刘志军:对标三大征信局与FICO 坚持风控能力输出
  6. 点击按钮测试用例标题_怎么写测试用例?
  7. Apache Kafka-通过设置Consumer Group实现广播模式
  8. EasyX识别不到VC++6.0
  9. 开源 RPC 框架有哪些呢?
  10. python 链表的中间节点