ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下。

先写个测试代码:

  1. import java.util.ArrayList;
  2. public class ArrayListRemove {
  3. public static void main(String[] args) {
  4. ArrayList<String> list = new ArrayList<String>();
  5. list.add("a");
  6. list.add("bb");
  7. list.add("bb");
  8. list.add("ccc");
  9. list.add("ccc");
  10. list.add("ccc");
  11. remove(list);
  12. for (String s : list) {
  13. System.out.println("element : " + s);
  14. }
  15. }
  16. public static void remove(ArrayList<String> list) {
  17. // TODO:
  18. }
  19. }

错误写法示例一:

  1. public static void remove(ArrayList<String> list) {
  2. for (int i = 0; i < list.size(); i++) {
  3. String s = list.get(i);
  4. if (s.equals("bb")) {
  5. list.remove(s);
  6. }
  7. }
  8. }

这种最普通的循环写法执行后会发现有一个“bb”的字符串没有删掉。

错误写法示例二:

  1. public static void remove(ArrayList<String> list) {
  2. for (String s : list) {
  3. if (s.equals("bb")) {
  4. list.remove(s);
  5. }
  6. }
  7. }

这种for each写法会发现报出著名的并发修改异常java.util.ConcurrentModificationException。

要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (int index = 0; index < size; index++)
  4. if (elementData[index] == null) {
  5. fastRemove(index);
  6. return true;
  7. }
  8. } else {
  9. for (int index = 0; index < size; index++)
  10. if (o.equals(elementData[index])) {
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }

按一般执行路径会走到else路径下最终调用faseRemove方法:

  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; // Let gc do its work
  8. }

可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。

针对这种情况可以倒序删除的方式来避免:

  1. public static void remove(ArrayList<String> list) {
  2. for (int i = list.size() - 1; i >= 0; i--) {
  3. String s = list.get(i);
  4. if (s.equals("bb")) {
  5. list.remove(s);
  6. }
  7. }
  8. }

因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。

而错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator<E>,看这个类的next方法:

  1. public E next() {
  2. checkForComodification();
  3. try {
  4. E next = get(cursor);
  5. lastRet = cursor++;
  6. return next;
  7. } catch (IndexOutOfBoundsException e) {
  8. checkForComodification();
  9. throw new NoSuchElementException();
  10. }
  11. }

第一行checkForComodification方法:

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

  1. public static void remove(ArrayList<String> list) {
  2. Iterator<String> it = list.iterator();
  3. while (it.hasNext()) {
  4. String s = it.next();
  5. if (s.equals("bb")) {
  6. it.remove();
  7. }
  8. }
  9. }

Java中ArrayList循环遍历并删除元素的陷阱的更多相关文章

  1. 【转】ArrayList循环遍历并删除元素的常见陷阱

    转自:https://my.oschina.net/u/2249714/blog/612753?p=1 在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出b ...

  2. ArrayList循环遍历并删除元素的常见陷阱

    在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug.不妨把这个问题当做一道面试题目,我想一定能难道不少的人.今天就给大家说一下在ArrayList循环 ...

  3. ArrayList循环遍历并删除元素的几种情况

    如下代码,想要循环删除列表中的元素b,该怎么处理? public class ListDemo { public static void main(String[] args) { ArrayList ...

  4. Java中List循环遍历的时候删除当前对象(自己)

    方法一 Java代码   ArrayList<String> list = new ArrayList<String>();           list.add(" ...

  5. Java HashMap 如何正确遍历并删除元素

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K ...

  6. Java中ArrayList边遍历边修改

    用for-each 边遍历ArrayList 边修改时: public static void main(String[] args) { ArrayList<String> list = ...

  7. Java中for循环遍历List的两种方法

    我们平常使用的方法: List<WebElement> element = driver.findElements(By.tagName("input"));      ...

  8. 【Java】List遍历时删除元素的正确方式

    当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...

  9. 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错

    一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...

随机推荐

  1. BZOJ5251 八省联考2018劈配(网络流)

    劈配,匹配,网络流.那么考虑怎么跑网络流. 先看第一问.首先套路的建出超源超汇.不用想也知道导师向汇连容量为战队人数上限的边.特别地,给出局也建一个点,向汇连容量inf的边(似乎没有必要).对于一个新 ...

  2. vmware错误汇总

    [问题来源] 因为虚拟机过大,所以直接在本地磁盘直接复制,启动的时候,换好IP重新启动网卡报错. device eth0 does not seem to be present.. ifconfig查 ...

  3. 有标号的DAG计数(FFT)

    有标号的DAG计数系列 有标号的DAG计数I 题意 给定一正整数\(n\),对\(n\)个点有标号的有向无环图(可以不连通)进行计数,输出答案\(mod \ 10007\)的结果.\(n\le 500 ...

  4. hdu3516 Tree Construction (区间dp+四边形优化)

    构造方法肯定是把相邻两个点连到一起,变成一个新点,然后再把新点和别的点连到一起.... 设f[i,j]为把第i到j个点都连到一起的代价,那么答案就是f[1,n] f[i,j]=min{f[i,k]+f ...

  5. Segment 李超线段树

    题目大意: 要求在平面直角坐标系下维护两个操作: 1.在平面上加入一条线段.记第 i 条被插入的线段的标号为 i 2.给定一个数 k,询问与直线 x = k 相交的线段中,交点最靠上的线段的编号. 若 ...

  6. Windows下修改Tomcat黑窗口标题

    在Tomcat的bin目录下,新建文件setenv.bat. 在文件内输入: set TITLE=MYTomcat-%date% %time%[%cd%] 可以把MYTomcat修改为自己定义的名字. ...

  7. [luogu3197][越狱]

    luogu3197 思路 看了很久没思路,看了题解发现自己好zz.用全部的情况减去不合法的情况就行了.全部的情况就是每个人随便选,总共有\(m^n\)种情况,然后考虑不合法的情况,也就是任意相邻的两个 ...

  8. (转)Java中equals和==、hashcode的区别

    背景:学习辉哥总结的基础知识,从头来,直面短板. 1 问题引入及分析 请看下面的代码清单1 @Test public void test01() { String a = "a" ...

  9. xtrabackup备份MySQL

    mysql备份之xtrabackup(建议用来备份innodb) 下载地址:https://www.percona.com/downloads/XtraBackup/ 安装xtrabackup [ro ...

  10. prometheus + grafana部署RabbitMQ监控

    prometheus + grafana部署RabbitMQ监控 1.grafana导入dashboards https://grafana.com/dashboards/2121   2.expor ...