Java8 学习笔记 - Iterable.forEach 和 for(item : list)的关系,及遍历中 remove
Iterable.forEach
Iterable
接口定义了 default
方法 forEach
它的参数类型是个Consumer 函数接口
。
继承关系:
废话不多说直接上的源码:
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
modCount
:List
所有修改都会 modCount++
( AbstractList 的保护属性 )
expectedModCount
:初始化Iterator
时用它记录下 modCount
的值。(AbstractList 下私有内部类 Itr 的属性)
- 在调用
迭代器
的remove
时会同步expectedModCount = modCount
。而List
的remove
不会。 迭代器
在调用next
时,会监测modCount != expectedModCount
。不一样,说明有别人改动过列表。(最常见的就是并发中多个线程互相冲突。)
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
ArrayList
获取迭代器是通过调用 iterator()
返回一个 Iterator
(Itr
的实例)。
创建迭代器时,其内部用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();
}
}
再来看这个遍历,其中有两部分:
- 遍历(底层实现就是迭代器)。这是迭代器在干活。
- 删除元素。这是List在干活。
- 这就导致了
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]