List如何一边遍历,一边删除

1、foreach抛出ConcurrentModificationException

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        for (String str : strList) {
            if ("Java".equals(str)) {
                strList.remove(str);
            }
        }
        System.out.println(strList);

报错信息如下:

在这里插入图片描述

在这里插入图片描述

通过其生成的字节码可以看出,foreach循环在实际执行时使用的是Iterator,使用的核心方法是hasnext()next()

ArrayList类中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
      	//初始化的时候expectedModCount等于modCount
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
          	//调用checkForComodification,校验modCount是否等于expectedModCount,如果不等于抛出异常ConcurrentModificationException
            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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

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

调用next()方法获取下一个元素时,调用了checkForComodification()校验modCount是否等于expectedModCount,如果不等于抛出异常ConcurrentModificationException

在上面的案例中,刚开始modCountexpectedModCount的值都为3,所以第1次获取元素是没问题的,但是当执行完strList.remove(str)modCount的值就被修改成了4

    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
    }

所以在第2次获取元素时,modCountexpectedModCount的值就不相等了,所以抛出了异常ConcurrentModificationException

那么List一边遍历一边删除正确的实现方法有哪些呢

1)、使用Iteratorremove()方法

2)、JDK1.8新增的removeIf()方法

3)、使用for循环正序遍历

4)、使用for循环倒序遍历

2、使用Iteratorremove()方法

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        Iterator<String> iterator = strList.iterator();
        while (iterator.hasNext()) {
            if ("Java".equals(iterator.next())) {
                iterator.remove();
            }
        }
        System.out.println(strList);

ArrayList中的Itr实现源码如下:

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

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
              	//expectedModCount、modCount这2个变量的值又相等了
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

每次删除一个元素,都会将modCount的值重新赋值给expectedModCount,这样2个变量就相等了

3、JDK1.8新增的removeIf()方法

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        strList.removeIf(str -> "Java".equals(str));
        System.out.println(strList);

或者

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        strList.removeIf("Java"::equals);
        System.out.println(strList);

Collection接口中removeIf()方法默认实现采用的也是Iteratorremove()方法

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

4、使用for循环正序遍历

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        for (int i = 0; i < strList.size(); ++i) {
            String str = strList.get(i);
            if ("Java".equals(str)) {
                strList.remove(i--);
            }
        }
        System.out.println(strList);

正序遍历需要修正下标,因为刚开始元素是:

0 Java
1 Docker
2 SpringCloud

第一次循环将Java删除后,元素的下标变成下面这样:

0 Docker
1 SpringCloud

如果不修正下标,第二次循环时i的值是1,也就是取到了SpringCloud,这样就导致元素Docker被跳过检查了

5、使用for循环倒序遍历

        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Docker");
        strList.add("SpringCloud");
        for (int i = strList.size() - 1; i >= 0; --i) {
            String str = strList.get(i);
            if ("SpringCloud".equals(str)) {
                strList.remove(i);
            }
        }
        System.out.println(strList);

倒序遍历不需要修正下标,因为刚开始元素是:

0 Java
1 Docker
2 SpringCloud

第一次循环将SpringCloud删除后,元素的下标变成下面这样:

0 Java
1 Docker

第二次循环时i的值是1,也就是取到了元素Docker,不会导致跳过元素,所以不需要修正下标

参考:

https://blog.csdn.net/zwwhnly/article/details/104987143

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/106736364
今日推荐