先来看一个例子:
public class TestFastFail {
public static void main(String[] args) {
testListRemove();
}
private static void testListRemove() {
List<String> list = new ArrayList<>();
list.add("zhuiangtao");
list.add("zhujiangtao");
list.add("pig");
list.add("ZHU");
for (String val : list) {
if (val.equals("zhujiangtao")) {
list.remove(val);
}
}
}
}
运行结果是:
为什么会如此呢?Java 中 foreach 具体是怎么实现的呢?
我们可以先将 .java 文件编译成.class文件,然后接着 jad工具反编译 .class文件为java 文件,具体步骤如下:
在 TestFastFail.java 文件所在的目录 打开cmd 命令窗口,执行命令:javac TestFastFail.java 生成 TestFastFail.class 文件
将之前生成的 TestFastFail.class 文件拷贝到 jad 解压后的文件夹内,执行如下命令:jad -s java TestFastFail.class 以生成 TestFastFail.java 文件。
反编译后的java文件如下:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: TestFastFail.java
package daily.yiyuan.com.test_java;
import java.util.*;
public class TestFastFail
{
public TestFastFail()
{
}
public static void main(String args[])
{
testListRemove();
}
private static void testListRemove()
{
ArrayList arraylist = new ArrayList();
arraylist.add("zhuiangtao");
arraylist.add("zhujiangtao");
arraylist.add("pig");
arraylist.add("ZHU");
Iterator iterator = arraylist.iterator();
do
{
if(!iterator.hasNext())
break;
String s = (String)iterator.next();//这里抛出的异常
if(s.equals("zhujiangtao"))
arraylist.remove(s);
} while(true);
}
}
可以看出来,foreach 其实是通过 do … while 和 Iterator 来实现的,但 删除 仍然调用的是 集合的 remove ,结果就是大家所看到的抛出了 ConcurrentModificationException异常。
为什么会抛出 ConcurrentModificationException ? 这个其实就是java的 fast-fail 机制,ArrayList 中 迭代器的 next 方法 会检查 ArrayList 中的 modCount 和 迭代器的 expectedModCount 是否相等,不相等就抛出异常。我们通过源码来分析下:
首先 我往链表里面 添加了 4 条数据,add 方法如下:
public boolean add(E var1) {
//扩容操作且 modCount +1
this.ensureCapacityInternal(this.size + 1);
this.elementData[this.size++] = var1;
return true;
}
ensureCapacityInternal 方法 如下:
private void ensureCapacityInternal(int var1) {
// calculateCapacity 计算 list 的大小 this.ensureExplicitCapacity(calculateCapacity(this.elementData, var1));
}
private void ensureExplicitCapacity(int var1) {
++this.modCount; // 加1 操作
//如果数组满了就扩容,这些很简单且不是本文的重点就不深入了
if (var1 - this.elementData.length > 0) {
this.grow(var1);
}
}
从上面的代码可知, 没调用一次 add 那么 ArrayList 的 modCount 就会加1。
remove 的方法如下:
public boolean remove(Object var1) {
int var2;
//如果 要删除的元素 是 null,因为 ArrayList 可以add null元素
if (var1 == null) {
for(var2 = 0; var2 < this.size; ++var2) {
//找到null元素且执行删除操作,真正的删除操作fastRemove
if (this.elementData[var2] == null) {
this.fastRemove(var2);
return true;
}
}
} else {//和上面是一样的,找到待删除元素后 执行删除操作
for(var2 = 0; var2 < this.size; ++var2) {
if (var1.equals(this.elementData[var2])) {
this.fastRemove(var2);
return true;
}
}
}
return false;
}
看看 真正执行的删除操作 fastRemove:
private void fastRemove(int var1) {
++this.modCount; // modCount 加1
int var2 = this.size - var1 - 1;
if (var2 > 0) {
//通过数组的复制来删除指定元素
System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
}
//数组最后一个位置置为null,方便GC回收
this.elementData[--this.size] = null;
}
上面的 remove 方法其他的我们不必关心,只要知道 remove 也会让 modCount 加1。
迭代器的 next 方法如下:
public E next() {
//首先会检查 modCount 和 迭代器 的属性 expectedModCount 是否相等,不相等就抛出异常
this.checkForComodification();
int var1 = this.cursor;
if (var1 >= ArrayList.this.size) {
throw new NoSuchElementException();
} else {
Object[] var2 = ArrayList.this.elementData;
if (var1 >= var2.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = var1 + 1;
return var2[this.lastRet = var1];
}
}
}
checkForComodification 代码如下:
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
这个很简单就不说了。主要看看 expectedModCount 在哪里赋值的呢? 是 list.iterator()方法赋值的,代码如下:
public Iterator<E> iterator() {
return new ArrayList.Itr();
}
Itr 是 ArrayList 的内部类,刚才的 next 方法就是此类实现的。Itr 中的属性不多,且只有一个构造函数,如下:
int cursor;
int lastRet = -1;
int expectedModCount; // 就是比较这个值和 ArrayList 的modCount 是否相等
Itr() {
//将ArrayList 的 modCount 赋值给 expectedModCount
this.expectedModCount = ArrayList.this.modCount;
}
通过以上的代码 我们就很清晰的知道 为什么 不能在 foreach 中执行集合的 add/remove 方法了。
为了便于分析,我们将反编译后的代码再复制一份到这里。
public class TestFastFail
{
public TestFastFail()
{
}
public static void main(String args[])
{
testListRemove();
}
private static void testListRemove()
{
ArrayList arraylist = new ArrayList();
arraylist.add("zhuiangtao");
arraylist.add("zhujiangtao");
arraylist.add("pig");
arraylist.add("ZHU");
Iterator iterator = arraylist.iterator();
do
{
if(!iterator.hasNext())
break;
String s = (String)iterator.next();
if(s.equals("zhujiangtao"))
arraylist.remove(s);
} while(true);
}
}
首先 执行了 4 次add ,那么 ArrayList 的 modCount 等于 4,
其次:Iterator iterator = arraylist.iterator(); 调用 Ite 的构造函数,把 modCount 赋值给 expectedModCount
然后:调用 next 获取集合的第一个值即“zhujiangtao”,进行比较,发现相等 那么执行集合的 remove 方法,由上面代码分析可知,执行 集合的 remove 方法后 modCount 会加1 即 modCount = 5了。
然后再一次循环执行next方法, 发现 modCount = 5, expectedModCount = 4 不相等了,所以抛出了ConcurrentModificationException
那么,正确删除集合的特定元素的姿势是什么呢?其实就是调用迭代器 的 remove()方法,如下:
List<String> list = new ArrayList<>();
list.add("zhuiangtao");
list.add("zhujiangtao");
list.add("pig");
list.add("ZHU");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String val = iterator.next();
if (val.equals("zhujiangtao")){
iterator.remove();
}
}
我们来看看 Iterator 的 remove() 方法,如下:
public void remove() {
// remove 是删除 next() 返回的元素,如果没有执行 next 那么 lastRet = -1,所以抛出异常,
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
//检查modCount 和 expectedModCount是否相等
this.checkForComodification();
try {
//调用 ArrayList.this.remove(int var1)方法,这个方法会使得 modCount + 1,
ArrayList.this.remove(this.lastRet);
this.cursor = this.lastRet;
this.lastRet = -1;
// expectedModCount 重新赋值 为 modCount, 所以下次循环调用next()方法时,不会抛出异常
this.expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
上面的注释已经解释的很清楚了,有不清楚的地方可以一起讨论下。