Java集合系列(一)Iterable & Iterator

本文探讨了Java中遍历并修改集合时遇到的问题,包括使用不同方式遍历集合时的注意事项,解释了ConcurrentModificationException异常产生的原因,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Iterable

Implementing this interface allows an object to be the target of the "for-each loop" statement

Iterable 接口中有两个默认方法和一个必须继承的方法:

Iterator<T> iterator();

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}
    
default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
复制代码

foreach 方法为我们在 JDK8以后使用 lambda 表达式提供了便利。

    LinkedList<Integer> list = LinkedList.newEmptyList();
    list.forEach(System.out::println);
    list.forEach(integer -> System.out.println(integer+1));
复制代码

Iterator

方法:

boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
复制代码

使用方式以及存在的问题

        List<Integer> integers = new ArrayList<>();
        for (int i = 0; i <= 50; i++) {
            integers.add(i);
        }

        for (int i = 50; i <= 100; i++) {
            integers.add(5);
        }
		
		// 正确删除某一个元素
        Iterator<Integer> iterator = integers.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer % 5 == 0) {
                iterator.remove();
            }else{
                System.out.println(integer);
            }
        }


		// 漏删元素,前一个元素被删除以后,integers.size() 减小,导致后一个元素可能逃过了被删除的命运。
        for (int i = 0; i < integers.size(); i++) {
            Integer integer = integers.get(i);
            if (integer % 5 == 0) {
                integers.remove(integer);
            }
        }
		// ... 48, 49, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
        System.out.println(integers);



		// fail-fast 错误:Exception in thread "main" java.util.ConcurrentModificationException
						  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
						  at java.util.ArrayList$Itr.next(ArrayList.java:851)
	    for (Integer integer : integers) {
           if (integer % 5 == 0) {
               integers.remove(integer);
               System.out.println("delete=========");
           }else{
               System.out.println(integer);
           }
        }
复制代码

我们留意一下最后一种方式的异常是在java.util.ArrayList$Itr.next(ArrayList.java:851)爆出的,从 log 信息上看,Itr 是 ArrayList 的内部类,我们进源码看看。

    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;

        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();
            }
        }
        
        final void checkForComodification() {
	        // 真正抛出异常的地方
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
复制代码

在上述内部类中,remove 方法内进行了一次 expectedModCount 和 modCount 的同步,那么这个 modCount 是什么呢?

   /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
     // 禁止 modCount 进行序列化操作
    protected transient int modCount = 0;
复制代码

简而言之,就是为了数据的安全,记录对集合的修改次数,并且在 next 、remove 等方法中监测,修改次数是否正确,不正确就会抛出异常。我们在上面的 remove 操作调用的是 ArrayList 的 remove 操作,remove 最终又调用了 fastRemove() 方法:

    private void fastRemove(int index) {
	    // 对 modCount 进行了更改
        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
    }
复制代码

到这里,真相已经大白了: 普通的 for-each 循环因为内部被优化成了使用 Iterator 模式进行迭代,ArrayList 在内部实现了一个内部类实现了 Iterator 接口,迭代的时候,调用的 next 方法就来自这个内部类,expectModCount 也维护在这个内部类的内部,我们在外部调用了 ArrayList 类的 remove 方法,修改了 ModCount ,最后导致在内部类中 checkForComodification 的时候,两个值不一样,就抛出了异常。所以,如果要在迭代的过程中修改集合,那么最好使用普通的 for 循环,并且注意漏删的问题,或者直接使用 Iterator 这种方式。

最后,在补充一下,为什么使用 Iterator 模式没有问题呢?

        Iterator<Integer> iterator = integers.iterator();
复制代码

这里的 iterator() 方法,点进去,是调用的 List 接口的 iterator()方法,查看这个方法的实现类,我们很快找到了 ArrayList,点进去:

    public Iterator<E> iterator() {
        return new Itr();
    }
复制代码

这里,使用的迭代器,就是我们 ArrayList 中实现了 Iterator接口的内部类,代码我们已经在上面贴过了。而从代码中,我们看到,这个内部类中,实现了一个 remove 方法,在方法中,删除以后,进行了 expectedModCount = modCount;的同步操作。所以,使用迭代器模式遍历并且删除ArrayList 集合元素是没有问题的,不会抛出 ConcurrentModificationException

        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();
            }
        }
复制代码

总结: 1、JDK1.8 以后,可以使用 lambda 表达式进行集合迭代 2、普通 for 循环迭代集合,对集合进行删除操作的时候,可能产生漏删的情况 3、for-each 循环中删除元素的时候,可能会发生 ConcurrentModificationException

转载于:https://juejin.im/post/5b501c2af265da0f6b76fcea

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值