Why Alibaba Java Development Manual mandatory Arrays.asList () can not be used to modify its method?

In reading "Ali Baba Java Development Manual", we found Statute article on elemental in foreach loop in the remove / add operation, as follows remove / add operations "Do not be an element in the foreach loop inside, remove elements, use Iterator way, if concurrent operations, the need for Iterator object locking ", then in the end why? Let's take the issue to find out.

In reading "Ali Baba Java Development Manual", were found on a statute element in the foreach loop in the remove / add operations, as follows:

Alibaba Java Development Manual

Error demo

We start by writing a code in IDEA remove operation in the foreach loop:

import java.util.ArrayList;import java.util.List;public class ForEachTest {    public static void main(String[] args) {        List<String> list = new ArrayList<>();        list.add("wupx");        list.add("love");        list.add("huxy");        for (String temp : list) {            if ("love".equals(temp)) {                list.remove(temp);            }        }        System.out.println(list);    }}

Run the code, compile properly executed successfully! Output [wupx, huxy].

Then we "love" replaced by "wupx" or "huxy" run again, the results are as follows:

Nani, actually being given, why the first run without being given it? Let's discuss it!

Tracing the source

In order to study why there is such a situation, we can abnormal stack information, to track errors, which related to the portion of the source code as follows:

private class Itr implements Iterator<E> {    int cursor;       // 下一个要返回的元素的索引    int lastRet = -1; // 返回的最后一个元素的索引(如果没有返回-1)    int expectedModCount = modCount;    public boolean hasNext() {        return cursor != size;    }    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++]);        }        cursor = i;        lastRet = i - 1;        checkForComodification();    }    @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];    }    final void checkForComodification() {        if (modCount != expectedModCount)            throw new ConcurrentModificationException();    }}

As can be seen from the code, actually maintains an initial value 0 is set when cursor cursor traversing, scanning is performed from start to finish, when the cursor == size, exit traversal. As shown below, after performing remove this element, all the elements forward copies, size = size-1 that is 2, then the cursor is also equal to 2. When performing the hasNext (), the result is false, the loop exits, and no chance to perform a next () of the first line of code checkForComodification (), and the method used to determine expectedModCount modCount are equal, if not equal, it is thrown ConcurrentModificationException exception.

cursor with the size of the collection

之所以会报 ConcurrentModificationException 异常,是因为触发了 Java 的 fail-fast 机制,该机制是集合中比较常见的错误检测机制,通常出现在遍历集合元素的过程中。举个生活中的栗子:

比如上体育课时,在上课前都会依次报数,如果在报数期间,有人突然加进来,还要重新报数,再次报数,又有同学溜出去了,又要重新报数,这就是 fail-fast 机制,它是对集合(班级同学)遍历操作的错误检测机制,在遍历中途出现意料之外的修改时,通过 unchecked 异常反馈出来。这种机制经常出现在多线程环境下,当前线程会维护一个计数比较器(expectedModCount),记录已经修改的次数。在进入遍历前,会把实时修改次数 modCount 赋值给 expectedModCount,如果这两个数据不相等,则抛出异常。java.util 下的所有集合类都是 fail-fast。

不二法门

既然在 foreach 循环里进行元素的 remove/add 操作会有问题,那么我们可以使用手册中推荐的 Iterator 机制进行遍历时的删除或新增,代码如下:

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class ForEachTest {    public static void main(String[] args) {        List<String> list = new ArrayList<>();        list.add("wupx");        list.add("love");        list.add("huxy");        Iterator iterator = list.iterator();        while (iterator.hasNext()) {            if (iterator.next().equals("wupx")) {                iterator.remove();            }        }        System.out.println(list);    }}

如果是多线程并发,还需要在 Iterator 遍历时加锁,或者使用并发容器 CopyOnWriteArrayList 代替 ArrayList,该容器内部会对 Iterator 进行加锁操作。

总结

本文针对《阿里巴巴 Java 开发手册》中的强制要求不要在 foreach 循环里进行元素的 remove/add 操作出发,从源码层面来解释为什么,还用生活中的栗子来介绍 Java 中的 fail-fast 机制,因此在进行元素的 remove/add 操作时要用 Iterator 去遍历删除或新增。

参考

《Java 开发手册》华山版

《码出高效:Java 开发手册》

阅读全文: http://gitbook.cn/gitchat/activity/5e53b68ae105cb3315778cec

You can also download CSDN community's quality original content GitChat App, read more GitChat exclusive technical content Oh.

FtooAtPSkEJwnW-9xkCLqSTRpBKX

Released 3730 original articles · won praise 3504 · Views 3.3 million +

Guess you like

Origin blog.csdn.net/valada/article/details/104488386