Analysis of the problem of remove from Java ArrayList in foreach

Source: Internet
Author: User
Tags concurrentmodificationexception

Directory

    • Iterator
    • Itr.hasnext and Itr.next implementations
    • The second-to-last element of a special
    • How to avoid the pit

All say ArrayList in the Foreach loop, can not add elements, and can not remove elements, may throw an exception, then we will analyze its specific implementation. My present environment is Java8.

There is the following section of code:

public class TestForEachList extends BaseTests {    @Test    public void testForeach() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        for (String s : list) {        }    }}

The code is simple, a ArrayList adds 3 elements, a foreach loop, and nothing is done. So how does foreach come into being, the brute force method to look at, compile and change the class, with javap -c TestForEachList the view class file bytecode, as follows:

Javap-c testforeachlist warning:binary file testforeachlist contains collection.list.TestForEachListCompiled from " Testforeachlist.java "public class Collection.list.TestForEachList extends Com.ferret.BaseTests {public    Collection.list.TestForEachList (); code:0: Aload_0 1:invokespecial #1//Method com/ferret/basetests. "    <init> ":() V 4:return public void Testforeach ();                  code:0: New #2//class java/util/arraylist 3:dup 4:invokespecial #3 Method java/util/arraylist. " <init> ":() V 7:astore_1 8:aload_1 9:ldc #4//String 1 11:invok      Einterface #5, 2//Interfacemethod Java/util/list.add: (ljava/lang/object;) Z 16:pop 17:aload_1 18:LDC #6//String 2 20:invokeinterface #5, 2//Interfacemethod Java/uti L/list.add: (ljava/lang/object;) Z 25:pop     26:aload_1 27:LDC #7//String 3 29:invokeinterface #5, 2//Inte            Rfacemethod Java/util/list.add: (ljava/lang/object;) Z 34:pop 35:aload_1 36:invokeinterface #8, 1      Interfacemethod java/util/list.iterator: () ljava/util/iterator;       41:astore_2 42:aload_2 43:invokeinterface #9, 1//Interfacemethod Java/util/iterator.hasnext: () Z 48:ifeq 51:aload_2 52:invokeinterface #10, 1//Interfacemethod Java/util/iterat      Or.next: () Ljava/lang/object; 57:checkcast #11//class java/lang/string 60:astore_3 61:goto 64:retur N

Can barely read, is called List.iterator , and then returns the result according to iterator's Hasnext method to determine if there is a next one, based on the next method to take the next element.

But is the experience is not good, we are modern, so with some modern means, directly with idea open the class file automatically decompile, get the following content:

public class TestForEachList extends BaseTests {    public TestForEachList() {    }    @Test    public void testForeach() {        List<String> list = new ArrayList();        list.add("1");        list.add("2");        list.add("3");        String var3;        for(Iterator var2 = list.iterator(); var2.hasNext(); var3 = (String)var2.next()) {            ;        }    }}

Experience a lot better, then compare the above bytecode file, yes

for(Iterator var2 = list.iterator(); var2.hasNext(); var3 = (String)var2.next()) {            ;        }

This is the real implementation of the foreach off the syntactic sugar shell.

Let's take a look at how these three methods are implemented:

Iterator

The iterator implementation of ArrayList is as follows:

public Iterator<E> iterator() {    return new 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;    //省略部分实现}

Itris the inner class in the ArrayList, so list.iterator() the function is to return an Itr object assignment to var2 , after the call var2.hasNext() , var2.next() is the implementation of ITR.

It is also worth mentioning that expectedModCount this variable record is assigned to modCount , modCount is a field of ArrayList's parent class abstractlist, the meaning of this field is the number of times the list structure has changed, This is usually triggered by an add or remove, which causes the number of elements to change modCount++ .

Next look at itr.hasNext()``var2.next() the implementation.

Itr.hasnext and Itr.next implementations

Hasnext is simple.

public boolean hasNext() {            return cursor != size;        }

The current index is not equal to size, which means that the size is the field of the outer class ArrayList, representing the number of elements.

Look at Next implementation:

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();        }

Next method The first step checkForComodification() , what does it do? If an modCount != expectedModCount exception is thrown ConcurrentModificationException . What is Modcount? The number of elements in the external class ArrayList; What is Expectedmodcount? Initializes the internal class ITR when the number of elements in the outer class changes.

Therefore, if you do an add or remove operation in foreach, the program will cause an exception ConcurrentModificationException . There are two examples to follow:

    @Test(expected = ConcurrentModificationException.class)    public void testListForeachRemoveThrow() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        for (String s : list) {            list.remove(s);        }    }    @Test(expected = ConcurrentModificationException.class)    public void testListForeachAddThrow() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        for (String s : list) {            list.add(s);        }    }

The unit test ran, and all was thrown ConcurrentModificationException .

checkForComodification()After the code is relatively simple here is not analyzed.

The second-to-last element of a special

Here, let's go through the general process:

    1. Gets the Itr object assigned to VAR2
    2. Judge Hasnext, that is, the cursor != size current iteration element subscript is not equal to the number of list, then return true to continue iterating;
    3. Next take out the iteration elements
      1. Checkforcomodification (), judging modCount != expectedModCount that the number of elements changed is not equal to the initialization of the internal class ITR when the number of changes in the element, that is, during the iteration has been modified to throw ConcurrentModificationException .
      2. If checked through cursor++

Consider the following scenario: What happens when you remove the second-to-last element? The code is as follows:

@Testpublic void testListForeachRemoveBack2NotThrow() {    List<String> list = new ArrayList<>();    list.add("1");    list.add("2");    list.add("3");    for (String s : list) {        System.out.println(s);        if ("2".equals(s)) {            list.remove(s);        }    }}

Guess what would throw an exception? The answer is in the negative. The output is:

1
2

Found 3 less output. Analyze

After the second-to-last element "2" remove, the list of the size-1 changed to 2, and at this time ITR in the next method to remove the element "2" after the addition of 1, the value becomes 2, resulting in the next Judgment Hasnext, Cursor==size, Hasnext returns false, eventually the last element is not output.

How to avoid the pit

Remove or add has a pit in foreach,

    • An exception is thrown when you do an operation (remove, add, and so on) that causes the number of elements to change in foreach ConcurrentModificationException
    • When you remove the second element in a foreach, it causes the last element to not be traversed

So how do we avoid it? We can use Fori without using foreach, the following code:

@Test    public void testListForiMiss() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        for (int i = 0; i < list.size(); i++) {            System.out.println(list.get(i));            list.remove(i);        }    }

It is obvious that the above is a false demonstration , the output is as follows:

1
3

The reason is very simple, the original element 1 is removed, the back of the forward copy, 2 to the original 1 position (subscript 0), 3 to the original position of 2 (subscript 1), size from 3 to 2,i+1=1, output list.get (1) became 3, 2 was omitted.

The following is the correct demonstration:

method One, or fori, the position before the move minus back on the line, remove after i--:

@Test    public void testListForiRight() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        for (int i = 0; i < list.size(); i++) {            System.out.println(list.get(i));            list.remove(i);            i--;  //位置前挪了减回去就行了        }    }

method Two, without the ArrayList remove method, with the ITR own definition of the Remove method , the code is as follows:

@Test    public void testIteratorRemove() {        List<String> list = new ArrayList<>();        list.add("1");        list.add("2");        list.add("3");        Iterator<String> itr = list.iterator();        while (itr.hasNext()) {            String s = itr.next();            System.out.println(s);            itr.remove();        }    }

Why is ITR's own definition of remove not an error? Look at the Source:

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();            }        }

There are still checkForComodification() checks, but after seeing that they are re-assigned, they are equally.

OK, the above is the whole content. Describes the pit of the list remove in foreach and how to avoid it.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.