[Java] Why Alibaba is prohibited in the foreach loop element in the remove / add operations

[Java] Why Alibaba is prohibited in the foreach loop element in the remove / add operations

foreach loop (Foreach loop) is a computer programming language statements in a control flow, typically used to loop through the array or set of elements.

Alibaba Java Developer's Handbook, there is such a provision:

Why Alibaba is prohibited in the foreach loop element in the remove / add operations

But the manual does not give a specific reason, we come to deeply analyze the thinking behind that provision.

1

foreach loop

foreach loop (Foreach loop) is a computer programming language statements in a control flow, typically used to loop through the array or set of elements.

Java language, JDK 1.5.0 from the beginning of the introduction of the foreach loop. In through the array, the collection aspect, foreach provide a great convenience for developers. Also commonly called enhanced for loop.

foreach syntax is as follows:

The following examples demonstrate common for foreach loop and recycling:

Output code is run results:

It can be seen using a foreach loop through the collection or array syntax, you can play the same effect and for general circulation, and the code more concise. So, foreach loop also is also commonly referred enhanced for loop.

However, as a qualified programmers, we must not only know what is enhanced for loop, you also need to know what the principle is enhanced for loop?

In fact, enhanced loop is a syntactic sugar for Java provides us with, if the class file after the above code is compiled to decompile (jad use tool), you can get the following code:

Can be found, the original enhanced for loop, the while loop in fact dependent and Iterator implementation. (Keep in mind that this implementation will be used later!)

2

To reproduce the problem

Specification states do not allow us to add / remove operations on the set of elements in a foreach loop, then we try to do it and see what will happen.

The above code, the first double parentheses syntax (double-brace syntax) to establish and initialize a List, which contains the four strings, respectively, Hollis, hollis, HollisChuang and H.

Then use ordinary for loop to traverse List, delete the List element's content equal to Hollis elements. Then output List, output results are as follows:

[hollis, HollisChuang, H]

These are the common use for loop to delete while traversing, then we look at, if using the enhanced for loop, then what happens:

The above code, using an enhanced for loop through the elements, and try to delete Hollis string elements therein. Run the above code will throw the following exception:

java.util.ConcurrentModificationException

Similarly, the reader can try to add elements in the enhanced for loop using the add method, the same results will be thrown.

The reason for this anomaly occurs because triggers an error detection mechanism --fail-fast a Java collection.

3

fail-fast

Next, we have to analyze the reasons at the time enhanced for loop add / remove elements throws java.util.ConcurrentModificationException, the next explanation in the end that is what is fail-fast binary, fail-fast principle and so on.

fail-fast, fail fast that is, it is a collection of Java error detection mechanism. When a set of a plurality of threads (non-fail-safe collection class) on the operation of changing the structure, there may have fail-fast mechanism, that is thrown when a ConcurrentModificationException (concurrent modification when the detected object, but out of the anomaly does not allow such modification).

Also note that, if not multi-threaded environment, if a single-threaded violates the rules, there are also likely to change throw an exception.

Well, elemental delete enhanced for loop, is a violation of the rules of how to do?

To analyze this question, we first enhanced for loop this syntactic sugar decompress sugar (use of class jad file compiled decompile), give the following code:

And then run the above code, it will throw an exception. We look at the complete stack of ConcurrentModificationException:

Why Alibaba is prohibited in the foreach loop element in the remove / add operations

By exception stack we can go to, line 23 call chain ForEachDemo exception occurs, Iterator.nextcall the Iterator.checkForComodificationmethod, but an exception is thrown checkForComodification method.

其实,经过debug后,我们可以发现,如果remove代码没有被执行过,iterator.next这一行是一直没报错的。抛异常的时机也正是remove执行之后的的那一次next方法的调用。

我们直接看下checkForComodification方法的代码,看下抛出异常的原因:

代码比较简单,modCount != expectedModCount的时候,就会抛出ConcurrentModificationException

那么,就来看一下,remove/add 操作室如何导致modCount和expectedModCount不相等的吧。

4

remove/add 做了什么

首先,我们要搞清楚的是,到底modCount和expectedModCount这两个变量都是个什么东西。

通过翻源码,我们可以发现:

  • modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。

  • expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。

  • Itr是一个Iterator的实现,使用ArrayList.iterator方法可以获取到的迭代器就是Itr类的实例。

他们之间的关系如下:

其实,看到这里,大概很多人都能猜到为什么remove/add 操作之后,会导致expectedModCount和modCount不想等了。

通过翻阅代码,我们也可以发现,remove方法核心逻辑如下:

Why Alibaba is prohibited in the foreach loop element in the remove / add operations

可以看到,它只修改了modCount,并没有对expectedModCount做任何操作。

简单总结一下,之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示用户,可能发生了并发修改。

5

正确姿势

至此,我们介绍清楚了不能在foreach循环体中直接对集合进行add/remove操作的原因。

但是,很多时候,我们是有需求需要过滤集合的,比如删除其中一部分元素,那么应该如何做呢?有几种方法可供参考:

1、直接使用普通for循环进行操作

我们说不能在foreach中进行,但是使用普通的for循环还是可以的,因为普通for循环并没有用到Iterator的遍历,所以压根就没有进行fail-fast的检验。

2、直接使用Iterator进行操作

除了直接使用普通for循环以外,我们还可以直接使用Iterator提供的remove方法。

如果直接使用Iterator提供的remove方法,那么就可以修改到expectedModCount的值。那么就不会再抛出异常了。其实现代码如下:

Why Alibaba is prohibited in the foreach loop element in the remove / add operations

3、使用Java 8中提供的filter过滤

Java 8中可以把集合转换成流,对于流有一种filter操作, 可以对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

4、直接使用fail-safe的集合类

在Java中,除了一些普通的集合类以外,还有一些采用了fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

Copy content is based on the advantages of avoiding ConcurrentModificationException, but again, the iterator does not have access to the modified content, namely: iterator traversal is to begin through the collection to get a copy of the moment, during the traversal of the original set of modifications happen iterator is not known.

Containers are safe under the java.util.concurrent package fails, you can use a multithreaded concurrent, concurrent modifications.

5, using the enhanced for loop in fact be

If we are very determined in a set, one about to delete the element contains only a word, such as for Set operation, so actually can use the enhanced for loop, long after the deletion, immediately ending loop, will not continue to traverse it, that is not to code execution to the next next method.

This can be avoided more than five ways to trigger the fail-fast mechanism to avoid throwing an exception. If concurrency scenarios, it is recommended to use a container concurrent package, if it is single-threaded scenarios, the previous code Java8, it is recommended to use an Iterator element removal, Java8 and updated version, you can consider using Stream and filter.

6

to sum up

We use the enhanced for loop, in fact, is syntactic sugar provided by Java, its implementation principle is to traverse the elements of the aid Iterator.

However, if during traversal, not through the Iterator, but add / delete the collection by the collection method of the class itself. So when a traversal in an Iterator, has been tested and found to modify the operation once the set is not by itself, it may be happening concurrently executed other threads will throw an exception this time, the user is prompted to occur concurrent modification, which is the so-called fail-fast mechanism.

Of course, there are many ways to solve these problems. For example, using an ordinary cycle, using an Iterator element removal, use of Stream filter, the use of fail-safe and the like.

Guess you like

Origin www.cnblogs.com/kise-ryota/p/11238628.html