目录

前言

第一种:普通for循环

第二种:利用迭代器

总而言之


前言

        在遍历Collection的过程中删去部分元素的情景是非常常见的,但是利用Java编写这样的程序,常常会出现一些让人很费解的问题。本文将阐释这些问题的产生原因以及解决办法。

        不妨以List作为例子,当我们需要对一个字符串数组删去其中包含"6."前缀的元素时,我们下意识会编写出这样的代码:

第一种:普通for循环

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("6.045");
        list.add("6.005");
        list.add("6.813");
        System.out.println("删除前:" + list);
        for(int i=0;i<list.size();i++)
        {
            String temp = list.get(i);
            if(temp.startsWith("6.")) //删去其中包含"6."前缀的元素
                //list.remove(i);
                list.remove(temp); //两种方法效果都一样
        }
        System.out.println("删除后:" + list);
    }
}

        这样的写法我们在C/C++里经常用,无非是多使用了Java自带的List.remove()的方法删去元素罢了。我们很当然的觉得程序运行的结果一定是:list中所有元素都被删除,然而....

Javalist遍历删除 java 遍历删除_Javalist遍历删除

        居然有一个元素没有被删除!这是为什么??

        最开始时,i=0,list.size()=3,temp=list[0]=6.045,包含"6."前缀,删去,i++;删除后,list中剩余所有元素都向前移了一位(索引-1:类似于取出队首元素,其他元素自动前进补齐空缺),此时i=1,list.size()=2,temp=list[0]=6.813,包含"6."前缀,删去,i++;此后list.size()=1,循环结束

Javalist遍历删除 java 遍历删除_Javalist遍历删除_02

        所以,其实问题出在了List.remove()方法使用后,元素的索引改变导致某些元素未被访问到,更改方式其实很简单——倒序遍历数组

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("6.045");
        list.add("6.005");
        list.add("6.813");
        System.out.println("删除前:" + list);
        for(int i = list.size() - 1; i >= 0; i--)
        {
            String temp = list.get(i);
            if(temp.startsWith("6."))
                list.remove(i);
                //list.remove(temp);
        }
        System.out.println("删除后:" + list);
    }
}

Javalist遍历删除 java 遍历删除_迭代器_03

 

        好耶,和预想的结果相同!!

第二种:利用迭代器

        如果是接触过迭代器的小伙伴,也可能写出这样的代码

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("6.045");
        list.add("6.005");
        list.add("6.813");
        System.out.println("删除前:" + list);
        Iterator<String> ite = list.iterator(); //迭代器
        while (ite.hasNext()) {
            String next =  ite.next();
            if(next.startsWith("6."))
                list.remove(next);
        }
        System.out.println("删除后:" + list);
    }
}

        或者引入写法比较简单的增强for循环【实际就是简化版的迭代器,底层实现相同】

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("6.045");
        list.add("6.005");
        list.add("6.813");
        System.out.println("删除前:" + list);
        for(String s:list) {
            if(s.startsWith("6."))
                list.remove(s);
        }
        System.out.println("删除后:" + list);
    }
}

        这样的写法真是方便又高级,但是怎么会报错啊...

Javalist遍历删除 java 遍历删除_List_04

        运行结果中抛出java.util.ConcurrentModificationException异常信息。这是因为触发了集合中并发修改的异常,接下来我们通过源码对抛出异常的原因进行剖析。 

public Iterator<E> iterator() {
        return new Itr();
    }

        在ArrayList集合的Iterator方法中,是通过返回Itr对象来获得迭代器的。ItrArrayList的一个内部类,它实现了Iterator接口,代码如下:

private class Itr implements Iterator<E> {
        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();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

Javalist遍历删除 java 遍历删除_迭代器_05

        ModCount定义在AbstractList接口中,初始值为0,定义如下:

protected transient int modCount = 0;

        ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。 

结合上述代码进行抛出 java.util.ConcurrentModificationException 异常的解释。
①初始化ArrayList,添加三次元素,即三次调用add()方法,进行三次 modCount++ ; 此时,modCount = 3 , size = 3 ;
②初始化Iterator迭代器进行循环,此时,expectedModCount = modCount = 3 ,cursor=0,lastRet=−1
③进行hasNext判断,cursor != size;成立,进入循环
④调用next()方法,首先进行checkForComodification()校验,modCount==expectedModCount,校验通过,返回值,此时lastRet=0;cursor=1

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

⑤调用集合remove()方法,modCount++; 此时modCount = 4 ; size = 2 

⑥再次调用hasNext()方法判断,cursor != size;成立,进入循环

⑦调用next()方法进行校验,modCount ! = expectedModCount

 总结:

Javalist遍历删除 java 遍历删除_Javalist遍历删除_06

        修改方法也很简单,使用迭代器自带的remove方法即可

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("6.045");
        list.add("6.005");
        list.add("6.813");
        System.out.println("删除前:" + list);
        Iterator<String> ite = list.iterator();
        while (ite.hasNext()) {
            String next =  ite.next();
            if(next.startsWith("6."))
                ite.remove(); //迭代器remove方法
        }
        System.out.println("删除后:" + list);
    }
}

Javalist遍历删除 java 遍历删除_迭代器_03

        可以看见,结果与预期相同,终于解决所有问题啦!

总而言之

        可行的办法分为两类:(1)普通for循环+倒序遍历+List.remove()

                                            (2)迭代器(非增强for循环)+迭代器remove()