一、背景

在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题

按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java.util.ConcurrentModificationException

但是下面这段代码实际情况却没报异常,是什么情况?

     List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
System.out.println("item:" + item);
if ("1".equals(item)) {
list.remove(item);
}
}

二、分析

我们知道ArrayList的foreach迭代调用的是ArrayList内部类Itr,Itr源码如下:

     private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @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];
} 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();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

按照调用顺序查看源码

1.hasNext

ArrayList的foreach迭代首先调用的是ArrayList内部类ItrhasNext(),该方法会对当前循环指针和长度做判断。只有当hasNext()返回true才会执行foreach里面的代码,cursor = size时候就退出循环(因为这两者相等意味着都遍历完了,假如ArrayList的size=2,那么hasNext会被调用3次,但是第3次调用不会执行foreach里面代码)。

2.如果上一步返回true的话会执行Itr的next(),如果数据无异常的话 cursor = i + 1;所以没执行一次next()时cursor都会+1

3.接着会执行到 list.remove(item),此处调用的是ArrayList的remove(Object o)而不是Itr的,看下ArrayList的remove()的源码:

 public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

o != null,会进入else的fastRemove(index);可以看到ArrayList根据传入的值删除会进行遍历equals判断,找到索引再通过fastRemove(index)删除,因此List频繁做删除修改效率比较低。

4.再看下fastRemove()源码:

    */
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

第8行会把该索引对应的数组的值置为null,并且size-1。但是却没有进行 cursor - 1操作

至此明白了。此处的ArrayList通过foreach迭代删除为什么不会报错:

刚开始ArrayList的size=2时,cursor =0

①第一次hasNext()进来,cursor != size进入next()后cursor=1,接着因为满足条件删除的时候size-1=1;

②第二次hasNext()进来,cursor = size = 1,所以不会执行foreach的代码,也不会出现后面检测modCount值抛ConcurrentModificationException

上述未抛异常的情况主要是hasNext()中判断遍历完成的条件与ArrayList删除后的数据刚好吻合而已。

所以只要满足条件:删除的元素在循环时的指针cursor+1=size就会出现这种情况!删除ArrayList倒数第二个(即第 size - 1个元素)就会出现不抛异常的假象。

(例如size=3,删除第2个元素;size=4,删除第3个元素)

因为删除后size-1=cursor

public boolean hasNext() {

return cursor != size;

}

hasNext()会返回false,不会进入ArrayList的迭代器,就不会进入 next() 执行checkForComodification()

这是一种条件判断下的特殊情况,其他情况都会抛出异常,所以不要在foreach进行删除数据。请在迭代器中进行删除。

ArrayList在foreach正常迭代删除不报错的原因的更多相关文章

  1. SharePoint 2013 删除母版页报错“This file may not be moved, deleted, renamed, or otherwise edited”

    在使用SharePoint 2013母版页的时候,我复制了一个seattle.master页面,然后想重命名一下发现报错,删除也报错,spd.页面分别试过签入签出以后均报错,错误如下: 尝试找了一下错 ...

  2. elasticsearch删除索引报错【原】

    如果elasticsearch删除索引报错 curl -X DELETE 'http://10.73.26.66:9200/httpd-34-2017.08.15' {"error" ...

  3. SpringBoot注册Windows服务和启动报错的原因

    SpringBoot注册Windows服务和启动报错的原因 Windows系统启动Java程序会弹出黑窗口.黑窗口有几点不好.首先它不美观:其次容易误点导致程序关闭:但最让我匪夷所思的是:将鼠标光标选 ...

  4. ionic build android 中的报错详细原因以及解决方法

    一.执行打包命令 ionic build android 1.报错: 原因: 其实也并非报错,但是会一直在下载gradle,由于网络或者其他原因,导致下载比较慢, 解决方案: 手动下载gradle,并 ...

  5. 服务端返回的json数据,导致前端报错的原因及解决方法

    前言 最近在开发的过程中遇到了一个问题:后端传过来的json字符串不是标准的json字符串 导致报错的原因 后端传过来的json字符串中包含一些不标准的字符或错误的引号嵌套 1)\n 2) \r 3) ...

  6. Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans

    Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans

  7. MariaDB:删除数据库报错:error: 'Error dropping database (can't rmdir './shiro', errno: 39)'

    今天在删除一个库的时候报错,如下图所示. 删除命名:mysqladmin –u root –p  drop shiro 解决办法: 删除./shiro目录下面的所有文件和目录. 重新执行删除命令即可!

  8. Docker删除镜像报错

    问题描述: 笔者意图删除nginx-file的镜像文件,但通过命令删除镜像时出现报错信息,提示存在多个引用(即一个IMAGE可被多个REPOSITORY引用,故删除会出现失败),如下: [root@k ...

  9. java操作数组转list集合删除元素报错ConcurrentModificationException

    public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>( ...

随机推荐

  1. Mysql修改已有数据的字符集

    Mysql修改已有数据的字符集 问题 在生产环境中跑了很久,发现MysqlClient连接的字符集是默认的latin1,我们一直以为都是utf8,造成这样的误解,是因为在内网环境中,我们是源码编译的M ...

  2. Oracle - java创建Oracle 的触发器

    Oracle - java创建Oracle 的触发器 今天碰到这个问题,遇到点问题,到这来 总结一下解决的办法, 需求,为一个用户当中的表增加一个自动增长列,我还没有学Oracle 的这部分,只是简单 ...

  3. [J2EE] 有关 PreparedStatement

    今天同事遇到一个问题,简言之,就是PreparedStatement的预编译究竟是怎么发挥作用的... 嘿嘿,说来惭愧,我以前就只知道PreparedStatement比Statement要好,要防S ...

  4. CSS实现父元素半透明,子元素不透明

    CSS实现父元素半透明,子元素不透明. 很久以来大家都习惯使用opacity:0.5在新式浏览器里实现半透明,而对IE较旧的版本使用filter:Alpha(opacity=0.5)的滤镜来实现半透明 ...

  5. 2 将mybatis配置到springmvc中

    为了更方便的连接数据库,将mybatis配置到springMVC中 1).  首先是jar包    多了3个jar  druid 这个是阿里的数据库连接包      mybatis和 mybatis- ...

  6. Gitpage + hexo(3.0以上)搭建博客

    大半天,一边折腾,一边查找各种文档,写出的这篇文档,不知道有没有把程序表示得足够简明,有不足之处望指明. 前提:已安装好nodeJS和git. 桌面右击进入gitbash,输入npm install ...

  7. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  8. 安装VisualSVN Server时候,端口号冲突

    今天在本机安装VisualSVN Server 时,发现https默认端口号:443被占用了, 于是到cmd下面执行 netstat -ano命令发现是pid:4276的进程在试用, 打开任务管理里一 ...

  9. springboot-mybatis 批量insert

    springboot mybatis 批量insert 操作 直接上代码: 1.首先要在pom.xml中导入包: 略...... 2.springboot mybatis配置: package com ...

  10. openGL光源概念

    1. 光照模型      环境光——经过多次反射而来的光称为环境光,无法确定其最初的方向,但当特定的光源关闭后,它们将消失. 全局环境光——每个光源都能对场景提供环境光.此外,还有一个环境光,它不来自 ...