一、前言

我们在日常开发中经常遇到循环里面删除数据的情况,但是如果我不注意使用方式的话很容易造成删除问题。我们学习下循环删除数据的正确方式。


二、普通for循环

package learn;

import java.util.ArrayList;
import java.util.List;

/**
 * @author qx
 * @date 2024/2/5
 * @des
 */
public class ForDemo {
    public static void main(String[] args) {
        List<String> list = initData();
        for (int i = 0; i < list.size(); i++) {
            // 删除列表第2个和第3个数据
            if (i == 1 || i == 2) {
                list.remove(i);
            }
        }
        System.out.println(list);
    }

    private static List<String> initData() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        return list;
    }
}

执行结果:

[aa, cc, ee]

我们的需求是要删除数据bb和cc但是却删除了bb和dd。原因就是我们删除第一个元素之后,集合重新进行了排序,打乱了之前的数据排序,删除bb后 顺序为[aa, cc,dd, ee],然后我们再删除索引为2的数据,所以就剩[aa, cc, ee]。所以这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。

三、高级for循环

package learn;

import java.util.ArrayList;
import java.util.List;

/**
 * @author qx
 * @date 2024/2/5
 * @des
 */
public class ForDemo {
    public static void main(String[] args) {
        List<String> list = initData();
        for (String s : list) {
            if ("bb".equals(s) || "cc".equals(s)) {
                list.remove(s);
            }
        }
        System.out.println(list);
    }

    private static List<String> initData() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        return list;
    }
}

执行结果:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at learn.ForDemo.main(ForDemo.java:14)

其实,基本上所有的集合类都会有一个叫做快速失败的校验机制,当一个集合在被多个线程修改并访问时,就会出现ConcurrentModificationException 校验机制。它的实现原理就是我们经常提到的modCount修改计数器。如果在读列表时,modCount发生变化则会抛出ConcurrentModificationException异常。这与线程同步是两码事,线程同步是为了保护集合中的数据不被脏读、脏写而设置的。

首先Java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator。iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

四、Iterator和removeif

iterator方式:

package learn;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author qx
 * @date 2024/2/5
 * @des
 */
public class ForDemo {
    public static void main(String[] args) {
        List<String> list = initData();
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String data = iterator.next();
            if ("bb".equals(data) || "cc".equals(data)) {
                iterator.remove();
            }
        }
        System.out.println(list);
    }

    private static List<String> initData() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        return list;
    }
}

执行结果正确:

[aa, dd, ee]

removeif方式

package learn;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author qx
 * @date 2024/2/5
 * @des
 */
public class ForDemo {
    public static void main(String[] args) {
        List<String> list = initData();
        list.removeIf(s -> "bb".equals(s) || "cc".equals(s));
        System.out.println(list);
    }

    private static List<String> initData() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        return list;
    }
}

执行结果正确:

[aa, dd, ee]

五、stream流处理

package learn;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author qx
 * @date 2024/2/5
 * @des
 */
public class ForDemo {
    public static void main(String[] args) {
        List<String> list = initData();
        list = list.stream().filter(s -> !"bb".equals(s) && !"cc".equals(s)).collect(Collectors.toList());
        System.out.println(list);
    }

    private static List<String> initData() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        return list;
    }
}

执行结果正确:

[aa, dd, ee]

六、总结

我们一般使用iterator迭代器的方式进行集合循环的删除。不考虑性能的时候使用removeIf方法,代码简洁明了。