为什么在foreach循环中JAVA集合不能添加或删除元素
1. 编码强制规约
在《阿里巴巴Java开发手册》中,针对集合操作,有一项规定,如下:
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
public class SimpleTest { public static void main(String[] args) { List<String> list = Lists.newArrayList(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); //正例 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("1".equalsIgnoreCase(item)) { iterator.remove(); } } //反例 for (String item : list) { if ("2".equals(item)) { list.remove(item); } } } }
2. 原因分析
在循环或迭代时,会首先创建一个迭代实例,这个迭代实例的expectedModCount 赋值为集合的modCount.
每当迭代器使⽤ hashNext() / next() 遍历下⼀个元素之前,都会检测 modCount 变量与expectedModCount 值是否相等,相等的话就返回遍历;否则就抛出异常【ConcurrentModificationException】,终⽌遍历
如果在循环中添加或删除元素,是直接调用集合的add,remove方法【导致了modCount增加或减少】,但这些方法不会修改迭代实例中的expectedModCount,导致在迭代实例中expectedModCount 与 modCount的值不相等,抛出ConcurrentModificationException异常
但迭代器中的remove,add方法,会在调用集合的remove,add方法后,将expectedModCount 重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。
可以参考ArrayList中的内部私有类Itr、ListItr的源码
public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.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; Itr() {} //删除了一些代码 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(); } } public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); 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 return oldValue; }
3. 相关知识介绍
3.1. 什么是快速失败(fail-fast)?
快速失败(fail-fast) 是 Java 集合的⼀种错误检测机制。在使⽤迭代器对集合进⾏遍历的时候,在多线程下操作⾮安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出ConcurrentModificationException 异常。
另外,在单线程下,如果在遍历过程中对集合对象的内容进⾏了修改的话也会触发 fail-fast 机制。
举个例⼦:多线程下,如果线程 1 正在对集合进⾏遍历,此时线程 2 对集合进⾏修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进⾏修改,都会导致线程 1 抛出ConcurrentModificationException 异常。
3.2. 什么是安全失败(fail-safe)呢?
采⽤安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,⽽是先复制原有集合内容,在拷⻉的集合上进⾏遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛ConcurrentModificationException 异常。
到此这篇关于为什么在foreach循环中JAVA集合不能添加或删除元素的文章就介绍到这了,更多相关JAVA集合添加或删除元素内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot feign动态设置数据源(https请求)
这篇文章主要介绍了SpringBoot如何在运行时feign动态添加数据源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下2021-08-08Activiti工作流学习笔记之自动生成28张数据库表的底层原理解析
这篇文章主要介绍了Activiti工作流学习笔记之自动生成28张数据库表的底层原理解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2021-03-03springMVC+velocity实现仿Datatables局部刷新分页方法
下面小编就为大家分享一篇springMVC+velocity实现仿Datatables局部刷新分页方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2018-02-02Springboot基于Redisson实现Redis分布式可重入锁源码解析
这篇文章主要介绍了Springboot基于Redisson实现Redis分布式可重入锁,本文通过案例源码分析给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-03-03详解Spring Data JPA使用@Query注解(Using @Query)
本篇文章主要介绍了详解Spring Data JPA使用@Query注解(Using @Query),具有一定的参考价值,有兴趣的可以了解一下2017-07-07
最新评论