JAVA集合为什么不能在foreach 循环中添加或删除元素?
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 异常。
相关文章
- java集合-遍历arraylist-for循环-从指定下标开始遍历-for的用法
- Java 开发环境配置--eclipse工具进行java开发
- java错误:The superclass "javax.servlet.http.HttpServlet" was not found on the Java Bu
- Java实现 蓝桥杯 算法训练 第五次作业:字符串排序
- Java实现 LeetCode 641 设计循环双端队列(暴力)
- Java实现 LeetCode 165 比较版本号
- Java实现 LeetCode 110 平衡二叉树
- Java实现 蓝桥杯VIP 算法训练 友好数
- [Linux] Install java and add JAVA_HOME, PATH
- Java中List效率的比较
- Java两种方法实现循环报数
- 【JAVA】增强for循环for(int a : arr)
- 【JAVA】java中的length和length()
- 【JAVA】毕向东Java基础视频教程-笔记
- Spring 基于 Java 的配置 - 如何不用Beans.xml照样描述bean之间的依赖关系
- 【java】EJB(Enterprise Java Bean)概述
- Simple Logging Facade for Java (SLF4J)作用(java日志框架)
- 详解jvm之java类加载机制和类加载器(ClassLoader) 深入理解Java类加载器(ClassLoader) 如何自定义类加载器 深入说明双亲委派 双亲委派模型的破坏者-线程上下文类加载器
- Java开发技术之成为高级java工程师必须学习的三个技术
- Java中的LinkedList
- java - 详解 Java 17 中新推出的密封类
- android studio DES加密编译的报错提示:java.security.InvalidKeyException: Wrong key size
- 【java】Java 重写(Override)与重载(Overload)
- 【java】Java 继承
- 【java】Java 集合框架
- 飞机大战Java版(Java+JavaSwing+多线程结构)