Collection class forEach in java reports an error when deleting elements: ConcurrentModificationException

    As the title shows, in our Java development, there may be a situation where a collection is used up and we want to delete all the elements in it. We may traverse them and then call the delete operation in sequence. The simplest we use forEach traversal.

    Examples are as follows:

public class ListForEachDel {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("name");
        list.add("id");
        System.out.println(list);
        list.forEach((item) -> {
            // todo
            list.remove(item);
        });
        System.out.println(list);
    }
}

    Run directly and report an error:

[name, id]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1262)
	at com.xxx.coll.ListForEachDel.main(ListForEachDel.java:22)

    For this problem, we clicked into the source code based on the error stack information and found that there will be a judgment that modCount is equal to expectedModCount in forEach:

    As soon as this method enters, it assigns expectedModCount = modCount. At the end, it should be equal if there are no other operations, but here is a delete operation, which will modify modCount:

    We called list.remove(item)

 

    The fastRemove(index) method is called in the remove(Object o) method:

   modCount is modified in fastRemove, so it is not equal to expectedModCount and an exception is thrown.

   In the same way, adding elements using the add method does not work. That is to say, the methods of adding and deleting elements cannot be used inside the forEach method body.

    So, is it okay to use a for loop? The answer is yes, but it depends on the situation:

    1. Delete subscript dynamic constraints from small to large. No error will be reported, but the deletion is not clean.

    2. Subscript static constraints are deleted from small to large, and an index out of bounds error will be reported.

    3. Delete the subscripts from large to small, you can delete them all, and no error will be reported.

   As shown below, it is a collection of three elements. During the deletion process, if list.size() dynamic constraints are used for subscript judgment, no error will be reported, but element 2 will be retained in the end. If you use static len=list.size() to judge, it will go out of bounds as the elements decrease.

    code show as below:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("before : " + list);
int len = list.size();
// i < list.size() 下标判断可以动态改变,不会出现越界问题,但是删不干净
// i < len 会出现下标越界
for (int i = 0; i < list.size(); i++) {
	list.remove(list.get(i));
}
System.out.println("after : " + list);

    No error is reported, but element deletion is retained:     

    Static subscript constraint, error:

 

    Deleting the first two will not cause an error. When it comes to the last element, the subscript increases to 2. In fact, there is only one element, which is out of bounds.

   

    The subscripts are deleted from large to small, which is normal.

    In fact, when traversing and deleting elements, it is generally recommended to use the iterator method. As shown below, the code for deleting array elements normally through iterators:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("before : " + list);
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
	iterator.next();
	iterator.remove();
}
System.out.println("after : " + list);

    operation result:

 

    This code looks a bit strange. Before calling iterator.remove(), we did the iterator.next() operation. What will happen if we accidentally remove this code?

    This is because in the Iter.remove() source code, lastRet=-1 will be modified after each operation.

 

    In addition, we can see here that after each deletion of iterator, expectedModCount=modCount will be modified, so that there will be no error like forEach deletion. 

     As mentioned above, Iter.remove() will change lastRet to -1, so that if you delete it again, an error will be reported directly. So why can the Iter.next() operation fix this error? We will know the reason by looking at the source code:

    In the last step of the source code, it assigns lastRet to a subscript. As long as the subscript does not cross the boundary, it will not be equal to -1.

    Just to mention it, here we are calling the remove() method of the iterator. What would happen if we were calling the remove(item) method of the collection?

    The same error as forEach was reported, because after the first deletion, modCount changed, and the next time next time, an error was reported directly.

    There is a special case, that is, when there are two elements, it does not report an error:

 

    What is the situation? We need to find the answer on Iter.hasNext():

    After a deletion here, because the next operation increases the cursor by 1, it happens to be 1, and the collection size also changes from 2 to 1, so the iterator judgment ends, and no code that may report an error is executed. I can only say here It's a coincidence. 

    Finally, let’s summarize the problem of collection traversal and deletion:

    foreach -> 1 Directly report an error ConcurrentModificationException
    iterator -> 1 No next operation will report an error IllegalStateException 
                       2 With next it is normal 
                       3 Calling list.remove(value) may not report an error, but there is still a problem.
    for -> 1 Deleting subscript dynamic constraints from small to large will result in incomplete deletion 
                       2 Deleting subscript static constraints from small to large will result in an out-of-bounds error IndexOutOfBoundsException 
                       3 All subscripts can be deleted from large to small

Guess you like

Origin blog.csdn.net/feinifi/article/details/131337889