Why can't an JAVA collection add or delete elements in an foreach loop
- 2021-09-16 06:57:51
- OfStack
1. Coding enforcement protocol
In Alibaba Java Development Manual, there is one regulation for collective operation, as follows:
"Force" not to perform remove/add operations on elements in an foreach loop. Use Iterator mode for remove elements, and lock Iterator objects if concurrent operations occur.
public class SimpleTest {
public static void main(String[] args) {
List<String> list = Lists.newArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
// Positive example
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("1".equalsIgnoreCase(item)) {
iterator.remove();
}
}
// Counterexample
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
}
}
}
}
2. Cause analysis
When you loop or iterate, you first create an iteration instance whose expectedModCount is assigned to the collection's modCount.
Every time the iterator makes hashNext ()/next () traverse the next element, it will detect whether the modCount variable is equal to the expectedModCount value, and if it is equal, it will return traversal; Otherwise, the exception "ConcurrentModificationException" is thrown, and the traversal ends
If an element is added or removed from the loop, the add and remove methods of the collection are called directly "causing modCount to increase or decrease", but these methods do not modify expectedModCount in the iteration instance, resulting in the value of expectedModCount and modCount being not equal in the iteration instance, and an ConcurrentModificationException exception is thrown
However, remove and add methods in the iterator will re-assign expectedModCount to modCount after calling remove and add methods of the collection, so adding and deleting elements in the iterator can run normally.
You can refer to the source code of the internal private classes Itr and ListItr in ArrayList
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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;
Itr() {}
// Deleted 1 Some codes
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();
}
}
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
3. Introduction of relevant knowledge
3.1. What is a quick failure (fail-fast)?
Fast Failure (fail-fast) is an error detection mechanism of the Java set. A collection class that fails to operate safely under multithreaded conditions (fail-safe) may trigger the fail-fast mechanism, causing an ConcurrentModificationException exception to be thrown, while the iterator is traversing the collection.
In addition, on a single thread, the fail-fast mechanism will also be triggered if the contents of the collection object are modified during traversal.
For example: Under multi-threading, if Thread 1 is traversing the collection, Thread 2 modifies (adds, deletes and modifies) the collection at this time, or Thread 1 modifies the collection during traversal, it will cause Thread 1 to throw ConcurrentModificationException exception.
3.2. What is a security failure (fail-safe)?
The collection container adopting the security failure mechanism is not directly accessed on the collection content during the traversal, but first copies the original collection content and traverses on the collection of copying. Therefore, modifications made to the original set during traversal cannot be detected by the iterator, so ConcurrentModificationException exception will not be thrown.