java8 foreach 删除一条数据_Foreach删除元素(ArrayList)报错分析

普通循环:利用index实现

增强型循环:通过迭代器实现

示例代码:

public class ArrayListTest {

public static void main(String[] args) {

normalFor(getList()); //普通循环

iterator(getList()); //增强循环-迭代器

forEach(getList()); //增强循环-foreach方式

}

//普通循环

private static void normalFor(List list) {

for (int i = 0 ; i < list.size() ; i++){

if ("b".equalsIgnoreCase(list.get(i)) || "c".equalsIgnoreCase(list.get(i))){

list.remove(i);

}

}

System.out.println("normalFor:"+JSONObject.toJSONString(list));

}

//增强循环-迭代器

private static void iterator(List list) {

Iterator iterator = list.iterator();

while (iterator.hasNext()){

String str = (String) iterator.next();

if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){

iterator.remove();

}

}

System.out.println("iterator:"+JSONObject.toJSONString(list));

}

//增强循环-foreach方式

private static void forEach(List list) {

for (String str : list){

if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){

list.remove(str);

}

}

System.out.println("forEach:"+JSONObject.toJSONString(list));

}

private static List getList(){

List list = new ArrayList<>();

list.add("a");

list.add("b");

list.add("c");

list.add("d");

list.add("e");

return list;

}

}

输出

normalFor:["a","c","d","e"]

iterator:["a","d","e"]

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at com.qxy.collection.ArrayListTest.forEach(ArrayListTest.java:47)

at com.qxy.collection.ArrayListTest.main(ArrayListTest.java:21)

Process finished with exit code 1

从上边可以看出

类型

输出结果

普通循环

正常输出,结果错误

增强循环-迭代器

正常输出,结果正确

增强循环-foreach

报异常

普通循环

普通循环,底层是数组,在remove操作时,被删除元素的后边所有的元素,会往前挪挪一位。咱们还是看图,比较直观

2027ad08f901ba8d72ca212fa1591539.png

当第一次删除时,此时的 i = 1,b正常删除,c、d、e此时都往前挪了一位,然后执行了 i+1 变成了2,也就是d 的位置,一直往后都没匹配到c,所以导致c为正常删除。

增强循环-迭代器

在分析之前,我们先来看看反编译之后的代代码

public class ArrayListTest {

...

private static void iterator(List list) {

Iterator iterator = list.iterator();

while(true) {

String str;

do {

if (!iterator.hasNext()) {

System.out.println("iterator:" + JSONObject.toJSONString(list));

return;

}

str = (String)iterator.next();

} while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));

iterator.remove();//不同的地方:调用迭代器的remove方法

}

}

private static void forEach(List list) {

Iterator var1 = list.iterator();

while(true) {

String str;

do {

if (!var1.hasNext()) {

System.out.println("forEach:" + JSONObject.toJSONString(list));

return;

}

str = (String)var1.next();

} while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));

list.remove(str);//不同的地方:调用list的remove方法

}

}

...

}

从上边的代码来看,迭代器 和 foreach 的方法很类似,唯一的区别就是 remove() 方法

迭代器调用的是  Iterator 类的 remove 方法

foreach调用的是 ArrayList类 的remove方法

那么我们去看下 他们各自 remove方法到底是怎么实现的

迭代器方式,那么需要先看 ArrayList.class

public Iterator iterator() {

return new Itr();

}

/**

* An optimized version of AbstractList.Itr

*/

private class Itr implements Iterator {

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);  //第二步:调用list的remove方法

cursor = lastRet;

lastRet = -1;

expectedModCount = modCount;    //第三步:modCount是remove方法去维护更新,由于第一步中校验 modCount 和 expectedModCount 是否相当等

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

...

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

可以看到,list.iterator() 返回的是一个 Itr对象(ArrayList私有的实例内部类),执行 iterator.remove() 方法时,

第一步:先调用 checkForComodification() 方法,此方法作用:modCount 和 expectedModCount 是否相当

第二步:也就是foreach方式中调用的remove方法,在ArrayList内部的remove方法,会更新modCount属性

第三步:将更新后的modCount重新赋值给expectedModCount变量,看这里!!!看这里!!!相比于有一个更新操作,才通过了上边第一步的校验!!!

到此,你可能还想看看,ArrayList类中的remove方法

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;

}

看到此方法中,有一个modCount++的操作,也就是说,modCount会一直更新变化。

总结:jdk源码离我们那么近,但是,总是这样完美的擦肩而过,以后要多啃啃,不要错过!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值