源码剖析-List遍历删除异常
情景
遍历List,且删除元素
-
普通for循环遍历删除
-
增强for循环遍历删除
-
Lambda forEach循环遍历删除
-
Lambda filter过滤
-
迭代器遍历删除
结果
-
普通for循环遍历删除,异常中断执行
-
增强for循环遍历删除,异常中断执行
-
Lambda forEach循环遍历删除,异常中断执行
-
Lambda filter过滤,正常执行
-
迭代器遍历删除,正常执行
异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at com.topsec.topsa.TopsaSearchApplication.main(TestApplication.java:77)
异常原因
-
list遍历下一个元素的时候,发现list本身的modCount != list内部迭代器的expectedModCount,抛出异常ConcurrentModificationException。
-
普通for循环删除元素,调用list本身的remove()方法,修改了modCount。
-
迭代器循环删除元素,调用迭代器的remove方法,方法里再通过调用list本身的remove()方法进行元素删除,修改了modCount,同时修改了迭代器的expectedModCount。
-
增强for循环删除元素,底层循环遍历数据是用的迭代器,删除数据用list本身的remove()方法,修改了modCount,但是无法修改迭代器的expectedModCount,因此遍历下一个元素的时候modCount!= expectedModCount,就会抛异常。
//代码 for (Integer integer : list) { list.remove(integer); } //反编译后 Iterator var4 = list.iterator(); while(var4.hasNext()) { Integer integer = (Integer)var4.next(); list.remove(integer); }
源码分析
ArrayList(remove)
/*删除此列表中指定位置的元素。将任何后续元素向左移动(从其索引中减去一)。
参数:index–要删除的元素的索引
返回值:从列表中删除的元素
抛出异常:IndexOutOfBoundsException–如果索引超出范围(index<0|| index>=size())
*/
public E remove(int index) {
//检查给定的索引是否在范围内
rangeCheck(index);
//此列表在结构上被修改的次数加1
modCount++;
//获取要删除的元素值
E oldValue = elementData(index);
//计算要移动元素的个数 list长度-要删除元素的下表-1
int numMoved = size - index - 1;
if (numMoved > 0)
//进行数组赋值,将数组中要删除元素后的元素整体向左移动,覆盖要删除的元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将数组末尾的元素置为null,同时便于jvm进行垃圾回收
elementData[--size] = null; // clear to let GC do its work
//返回已删除的元素值
return oldValue;
}
/*
从该列表中删除指定元素的第一个出现(如果存在)。如果列表中不包含该元素,则该元素将保持不变。更正式地说,删除索引i最低的元素,使得(o==null?get(i)==null:o.equals(get(i)))(如果存在这样的元素)。如果此列表包含指定的元素(或者等效地,如果此列表因调用而更改),则返回true。
参数:o–要从此列表中删除的元素(如果存在)
返回值:如果此列表包含指定的元素,则为true
*/
public boolean remove(Object o) {
if (o == null) {
//当参数等于null时,遍历数组,找到值等于null的元素,将之删除
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//当参数不等于null时,遍历数组,找到值等于给定参数值的元素,将之删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
跳过边界检查而不执行的私有remove方法
并且不返回被删除的值
*/
private void fastRemove(int index) {
//此列表在结构上被修改的次数加1
modCount++;
//计算要移动元素的个数 list长度-要删除元素的下表-1
int numMoved = size - index - 1;
if (numMoved > 0)
//进行数组赋值,将数组中要删除元素后的元素整体向左移动,覆盖要删除的元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将数组末尾的元素置为null,同时便于jvm进行垃圾回收
elementData[--size] = null; // clear to let GC do its work
}
/**
检查给定的索引是否在范围内。如果没有,则抛出适当的运行时异常。此方法不*检查索引是否为负数:它总是在数组访问之前使用,如果索引为负数,则会抛出ArrayIndexOutOfBoundsException。
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
ArrayList内部类迭代器(Itr)
/**
AbstractList.Itr的一个版本
*/
private class Itr implements Iterator<E> {
//访问列表下一个元素的下标,默认等于0
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 boolean hasNext() {
return cursor != size;
}
//获取列表下一个元素
@SuppressWarnings("unchecked")
public E next() {
//列表预期修改次数与实际修改次数检查
checkForComodification();
//将访问列表下一个元素的下标赋值给i
int i = cursor;
//如果下一个元素的下表大于等于列表的长度,则抛异常
if (i >= size)
throw new NoSuchElementException();
//将列表赋值给elementData
Object[] elementData = ArrayList.this.elementData;
//如果下一个元素的下表大于等于列表的长度,则抛异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
//将访问下一个元素的下标加1
cursor = i + 1;
//将i赋值给lastRet,并获取当前下标位置的元素
return (E) elementData[lastRet = i];
}
//元素删除
public void remove() {
//如果遍历访问的最后一个元素的下标小于0,则抛异常
if (lastRet < 0)
throw new IllegalStateException();
//列表预期修改次数与实际修改次数检查
checkForComodification();
try {
//调用ArrayList的remove方法,删除下标等于迭代器当前访问元素的下标
ArrayList.this.remove(lastRet);
//迭代器当前访问元素的下标,赋值给当前的访问列表下一个元素的下标
cursor = lastRet;
//将迭代器当前访问元素的下标赋值为-1
lastRet = -1;
//将列表实际修改次数,与迭代器中的预期修改次数同步
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//迭代器函数式遍历列表元素
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
//列表长度
final int size = ArrayList.this.size;
//将访问列表下一个元素的下标赋值给i
int i = cursor;
//如果已经访问到最后一个元素,则程序停止
if (i >= size) {
return;
}
//将列表赋值给elementData
final Object[] elementData = ArrayList.this.elementData;
//如果下一个元素的下表大于等于列表的长度,则抛异常
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
//当下一个元素的下表不等于列表的长度,且列表预期修改次数与实际修改次数相等
//迭代遍历列表元素
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
//在迭代结束时更新一次以减少堆写入流量
//赋值访问列表下一个元素的下标为列表长度
cursor = i;
//赋值迭代遍历当前元素的下标为列表末尾
lastRet = i - 1;
//列表预期修改次数与实际修改次数检查
checkForComodification();
}
//列表预期修改次数与实际修改次数检查
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}