Java8 学习笔记 - Iterable.forEach() 和 for(item : list) 的关系,及遍历中 remove

Iterable.forEach

Iterable 接口定义了 default 方法 forEach 它的参数类型是个Consumer 函数接口
继承关系:

继承
继承
List
Collection
Iterable

废话不多说直接上的源码:

public interface Iterable<T> {

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

既然是在 for(item : list) 上封装的,那 remove 的老问题还是一样的:异常。

@Test
public void testForEach(){
    List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
    numbers.forEach(i -> {
        if(i == 2){
            numbers.remove(i); // java.util.ConcurrentModificationException
        }
    });
}

分析 forEach 遍历中 remove 的坑

ConcurrentModificationException 的本意
通过 modCount 的值来检查到并发翻车的情况,实现快速失败。

modCount 与 expectedModCount

modCountList所有修改都会 modCount++ ( AbstractList 的保护属性 )
expectedModCount:初始化Iterator时用它记录下 modCount 的值。(AbstractList 下私有内部类 Itr 的属性)

  1. 在调用迭代器remove时会同步expectedModCount = modCount。而Listremove不会。
  2. 迭代器在调用next时,会监测modCount != expectedModCount。不一样,说明有别人改动过列表。(最常见的就是并发中多个线程互相冲突。)
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

ArrayList 获取迭代器是通过调用 iterator() 返回一个 IteratorItr 的实例)。
创建迭代器时,其内部用expectedModCount 记录了当前modCount的值。
list所返回的迭代器Itr的源码可以看出,这几个方法都会做检测。如果有并发操作,就会抛异常。

private class Itr implements Iterator<E> {
	int expectedModCount = modCount;

    public E next() {
        checkForComodification();
        ...
    }

    public void remove() {
        checkForComodification();
        ...
    }

    public void forEachRemaining(Consumer<? super E> consumer) {
        checkForComodification();
        ...
    }

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

再来看这个遍历,其中有两部分:

  1. 遍历(底层实现就是迭代器)。这是迭代器在干活。
  2. 删除元素。这是List在干活。
  3. 这就导致了 modCount != expectedModCount
for (T t : list) {  // 2. 迭代器再次 next 时检查会发现 modCount != expectedModCount
    list.remove(t); // 1. List 的 remove 方法修改了 modCount
}

所以想要删除元素时,我们只能调用迭代器的 remove 。

那如何在遍历中删除呢?

用传统 for 循环

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (int i = 0; i < numbers.size();  i++) {
    if (numbers.get(i) == 2) {
        numbers.remove(i);
    }
}
System.out.println(numbers);

用迭代器

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()){
    if (iterator.next() == 2) {
        iterator.remove();
    }
}
System.out.println(numbers);

用 Java8 新增的默认方法 removeIf

如果只是单纯的按条件 remove 元素,推荐来自 Collection Java8 新增的默认方法removeIf
参数为一个 Predicate 就是一个作为过滤器的 lambda 它返回 boolean

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
numbers.removeIf(i -> i==2);
System.out.println(numbers); // [1, 3, 4]

参考资料

Interface Collection<E>
Interface Iterable<T>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑虾

多情黯叹痴情癫。情癫苦笑多情难

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值