java实战-list遍历删除元素

1. 简述:

在我们的实际开发中,list绝对是用的最多的集合类,然而对于很多人来说,用的最多的出错也是最多的。尤其是list的增删操作。

2. 错误示例

public class Test {
    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(1);
        list.add(3);
        list.add(4);
        list.add(1);
        list.add(5);
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
            if(1==list.get(i)){
                list.remove(i);
            }
        }
        System.out.println("当前list:"+list.toString());
    }
}

运行结果:

1
3
4
1
当前list:[1, 3, 4, 5]

list里一共有6个元素,遍历加删除结果就4个,而且 元素值5还不见了,这种bug对于新手来说比抛异常难受多了呢(我明明有数据,怎么展示出来就没了,我又没删除5,最终的list怎么还有1!!!)。
这个时候有“聪明的小伙伴”就想到了6个元素,我可以这样改:

List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(1);
        list.add(3);
        list.add(4);
        list.add(1);
        list.add(5);
        for(int i = 0;i<6;i++){
            System.out.println(list.get(i));
            if(1==list.get(i)){
                list.remove(i);
            }
        }
        System.out.println("当前list:"+list.toString());

结果:

1
3
4
1

Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.Test.main(Test.java:29)

Process finished with exit code 1

终于报异常了呢,好开心。

3. 上ArrayList源码

    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,是这样描述的:
此列表在结构上被修改的次数。结构修改是指改变列表大小的修改,或者以其他方式扰乱列表,使得正在进行的迭代可能会产生不正确的结果。此字段由迭代器和listIterator方法返回的迭代器和列表迭代器实现使用。如果此字段的值意外更改,迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应下一个、删除、上一个、设置或添加操作。这提供了快速失败的行为,而不是在迭代过程中面对并发修改时的非确定性行为。子类是否使用此字段是可选的。如果一个子类希望提供快速失败迭代器(和列表迭代器),那么它只需在其add(int,E)和remove(int)方法(以及它覆盖的任何其他导致列表结构修改的方法)中递增此字段。单次调用add(int,E)或remove(int)必须向此字段添加不超过一个,否则迭代器(和列表迭代器)将抛出虚假的ConcurrentModificationExceptions。如果实现不希望提供快速失败迭代器,则可以忽略此字段。
很明显一边遍历一边变更增删元素,数组的长度都会改变。那一边遍历一边增删必然会出现很多问题。

4. 正确方式一:

        List<Integer> list = new ArrayList<>();
       list.add(1);
       list.add(1);
       list.add(3);
       list.add(4);
       list.add(1);
       list.add(5);
       Iterator<Integer> iterator = list.listIterator();
       while (iterator.hasNext()) {
           Integer value = iterator.next();
           System.out.println(value);
           if (value.equals(1)) {//1是要删除的元素
               iterator.remove();
           }

       }
       System.out.println("当前list是"+list.toString());
   }

结果

1
1
3
4
1
5
当前list是[3, 4, 5]
利用Iterator进行操作(不熟悉迭代器的小伙伴这时候就需要学习了)来避免list方法中改变数组长度。

5. 正确方式二:

List<Integer> list = new ArrayList<>();
        List<Integer> list1 = new ArrayList<>();
        list.add(1);
        list.add(1);
        list.add(3);
        list.add(4);
        list.add(1);
        list.add(5);
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
            if(1==list.get(i)){
                continue;
            }
            list1.add(list.get(i));
        }
        System.out.println("当前list1:"+list1.toString());

结果:

1
1
3
4
1
5
当前list1:[3, 4, 5]
Disconnected from the target VM, address: '127.0.0.1:56792', transport: 'socket'

Process finished with exit code 0

利用另一个list做容器,这样就避免了同一个list一边遍历一边删除。

6. 总结

首先list不可以一边遍历一边增删,看到list就多长个心眼:其他集合类有没有可能也有这种情况!!!
其次,正确的实现方式不仅仅是我展示的两个 ,stream也可以用等等。
最后,从实战角度来说,我们操作的list远比示例的复杂,多数情况是list中所有:一个对象转换成另一个对象,符合要求的转,不符合要求的不要,Iterator“好看但往往不符合实际使用”,利用另一个list做容器的方式才是我们代码里最常出现的方式。
希望这篇文章对你有帮助。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值