Java中List遍历删除元素

阿里的Java编程规约中有一条:【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

整理一下foreach中所出现的坑,以及为什么使用迭代器删除的原因

1、使用foreach循环删除操作出现的问题

首先总结下原因:是因为遍历的时候remove方法并不会修改expectedModCount 的值,在foreach方法属性值检查中抛出异常。

//测试代码
ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);   
        //直接使用List的remove方法会出现问题     
        list.forEach((e)->{
            if(e.equals((1))){
                list.remove(e);
            }
        });

此时会报类型修改异常

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1260)
	at Test.main(Test.java:16)

首先看ArrayList中forEach的源码:

@Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        //最后两行检查集合结构修改次数和期望修改次数是否相等
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

List的remove()方法不会修改expectedModCount的值,只会修改modCount,最后检查不通过抛出异常。

参考文章探索:删除集合中倒数第二个元素时,并不会报异常的问题。
首先看问题代码:

ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        for (Integer integer : list) {
            if(integer.equals(1))
                list.remove(integer);
        }

当删除头尾元素时,会报异常:

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 Test.main(Test.java:15)

可以看出与上面使用foreach方法报的异常信息不同。此时使用增强for循环进行遍历,反编译之后是使用迭代器来进行遍历,迭代器next()方法中的checkForComodification()会检查modCount和expectedModCount是否相等。此时判断会报错。

此时若删除的是3(集合中倒数第二个元素),程序能够正常退出

从此时反编译的字节码中寻找答案:当删除元素3之后,数组的size会减1,再次进入循环之后,迭代器的hasNext()方法会检查集合size和此时的cur是否相等,由于删除3之后,size-1=3 此时的cur也等于3,造成没有遍历到最后一个元素,循环就结束终止。

在这里插入图片描述

2、正确的删除方法

//使用迭代器删除
ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
            Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            if(iterator.next().equals(1)){
                iterator.remove();
            }
        }

总结

迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,该方法主要就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 异常。而remove方法只修改modCount的值。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值