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