根据List中对象的属性值删除List中元素的正确操作

271 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

使用增强for循环、forEach循环、迭代器调用List自身的remove()会抛ConcurrentModificationException异常,正确操作案例如下:

例如:将状态为DEL的用户从List中删除。 userList为原始List数据,返回操作后的数据。

解决方案:

方法一:使用迭代器的Iterator.remove()删除

List<User> userList = userService.getUserList();
Iterator<User> iterator = userList.iterator();
while (iterator.hasNext()) {
    User user = iterator.next();
    //将status为DEL的删除
    if (StatusEnum.DEL.equals(user.getStatus())) {
        //userList.remove(user); //ConcurrentModificationException
        iterator.remove();
    }
}
return userList;

方法二:使用stream过滤(JDK8+)

List<User> userList = userService.getUserList();
List<User> list = userList.stream().filter(user -> !StatusEnum.DEL.equals(user.getStatus())).collect(Collectors.toList());
return list;

方法三:使用逆序for循环调用List.remove(Object)删除

正序for循环需要注意索引问题。

List<User> userList = userService.getUserList();
for (int i = userList.size() - 1; i >= 0; i--) {
    User user = userList.get(i);
    //将status为DEL的删除
    if (StatusEnum.DEL.equals(user.getStatus())) {
        userList.remove(user);
    }
}
return userList;

方法四:使用Collection的removeIf()删除(JDK8+)

Collection<User> userList = userService.getUserList();
//将status为DEL的删除
userList.removeIf(user -> StatusEnum.DEL.equals(user.getStatus()));
return Lists.newArrayList(userList);
D: \Soft\jdk1.8.271\bin\java.exe . . .

java.util.ConcurrentModificationException <Create breakpoint>
    at java.util.ArrayList$Itr.checkForComodification (ArrayList.java:911)
    at java.util.ArrayList$Itr.next(ArrayList.java:861)
    at AppTest.testList(AppTest.java:347) <22 internal calls>

为什么会抛异常?

对于List自身的remove()方法,通过异常信息可以看到异常信息抛出是在java.util.ArrayList$Itr.checkForComodification()方法,对应remove方法源码如下,在删除时做了modCount++操作,接下来next()方法开始就调用了checkForComodification()校验modCountexpectedModCount是否相等,不相等就会抛出异常。

expectedModCount:是ArrayList修改次数的初始值,它的初始值为modCount=0。 modCount:是AbstractList类中的一个成员变量,该值表示对List的修改次数,每次调用add()或者remove()就会对modCount进行加1操作。

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

private void fastRemove(int index) {
    modCount++;
    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
}

private class Itr implements Iterator<E> {

    @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();
        }
    }

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

通过源码我们会发现List内部有一个Itr迭代器也提供了remove()方法,这个方法在删除后做了一个操作expectedModCount = modCount;,所以只要调用迭代器的remove()方法或重新刷新list再删除就不会有问题。