对Collections的遍历删除方式

以collections的子类List(Arraylist)为例

List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        for (String s:list) {
            if (s.equals("d")) {
                list.remove(s);
            }
        }
        System.out.println(list);

做一个遍历的操作,如果list中的某个元素满足某个条件,则将该元素从list中移除。

经过测试运行,可以发现,除非说要移除的元素位于倒数第二的元素,否则会出现异常,报错如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.util.ArrayList$Itr.next(Unknown Source)
	at Main.main(Main.java:49)

再看如下一例子

List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");
        for (String s:list) {
            if (s.equals("c")) {
                list.remove(s);
            }
        }
        System.out.println(list);

期望得到的list应该只包含“a”,“b”两个元素,然而得到的是如下的list:

[a, b, c]

解决办法:

定义一个新的list,将要删除的list放在这个新定义的list中,最后调用removeAll的方法整体删除

List<String> dele=new ArrayList<>();
        List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");
        for (String s:list) {
            if (s.equals("c")) {
                dele.add(s);
            }
        }
        list.removeAll(dele);
        System.out.println(list);

错误分析:

首先知道for—each循环是使用迭代器来实现的,找到源码

//    在AbstractList类中  
//    内部成员有:  
//    protected transient int modCount = 0;  
      
//    注:该modCount相当于集合的版本号,集合内容每改变一次,其值就+1。  
//    观察下面内部类可以知道,在next()、remove()方法都会去判断集合版本号与迭代器版本号是否一致checkForComodification();  
//    内部方法有:  
    public Iterator<E> iterator() {  
        return new Itr();  
        }  

    private class Itr implements Iterator<E> {  
        int cursor = 0;  
        int lastRet = -1;  
        //***创建迭代器时就讲版本号给了迭代器  
        int expectedModCount = modCount;  
      
        public boolean hasNext() {  
                return cursor != size();  
        }  
      
        public E next() {  
                checkForComodification();  
            try {  
            E next = get(cursor);  
            lastRet = cursor++;  
            return next;  
            } catch (IndexOutOfBoundsException e) {  
            checkForComodification();  
            throw new NoSuchElementException();  
            }  
        }  
      
        public void remove() {  
            if (lastRet == -1)  
            throw new IllegalStateException();  
                checkForComodification();  
      
            try {  
            AbstractList.this.remove(lastRet);  
            if (lastRet < cursor)  
                cursor--;  
            lastRet = -1;  
            expectedModCount = modCount;//迭代器进行删除时,会改变it的版本  
            } catch (IndexOutOfBoundsException e) {  
            throw new ConcurrentModificationException();  
            }  
        }  
        //检查迭代器的版本与集合的版本是否一致,不同则抛出异常  
        final void checkForComodification() {  
            if (modCount != expectedModCount)  
            throw new ConcurrentModificationException();  
        }  
     }  

需要知道两点:

1.modCount,每一次list改变都会进行使其+1

2.cusor,光标,新遍历元素时候+1

该案列中,list.size()=4,遍历集合时创建集合迭代器,此时有 expectedModCount = modCount=4,迭代器的cursor=0,

1cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(2cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(3cursor=2,it.next()检查版本号一致,遍历第三个元素,cursor=cursor+1=3,进入if语句,移除一个元素,此时集合内容改变,版本号modCount++,5.然后it.hasNext()判断cursor(2)!=size(4),true

3cursor=3,it.next()检查发现版本号不一致expectedModCount! = modCount,抛出并发修改异常。

而我们所举的第二个例子可以看出,若要删除的元素位置在倒数第二个中,则遍历会提前结束,原因:

(1cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(2cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(3cursor=3,it.next()检查版本号一致,遍历到要删除的元素,cursor=cusor+1=3;进入if语句,list.remove(),此时,版本号modCount++,且集合大小改变list.size()=3,然后,it.hasNext()判断发现cursor(3)==size(3),返回false,迭代提前结束

因此不会爆出异常

原文地址:https://www.cnblogs.com/masteryellow/p/8661751.html