java Foundation: About traversal -- for, foreach, and Iterator

Keywords: Java

Three forms of traversal

In java, there are three forms of traversal of arrays and container classes, for example:

     ArrayList<String> arr = new ArrayList<String>();
     arr.add("1");
     arr.add("2");
     arr.add("3");
     arr.add("4");
  • for form traversal
     for (int i = 0 ;i < arr.size();i++ ){
          System.out.println(arr.get(i));
      }
  • foreach form traversal
    for (String str: arr) {
      System.out.println(str);
    }
  • Iterator form traversal
    while (iterator.hasNext()){
           System.out.println("size : "+arr.size());
       }

From the way the code is written, it is not difficult to see that the for form must know the number of elements to be traversed (i < arr. size () will be judged in each cycle), while the other two ways do not need to know the number of elements to be traversed. At this point, a question arises: What about adding or deleting elements in a loop?

Adding or deleting elements in traversal

Conjecture: Because for loop form checks element length every time it cycles, it should not throw an exception. What about the other two forms? Will there be any abnormalities?

  • Delete in for loop
    for (int i = 0 ;i < arr.size();i++ ){
        System.out.println(arr.get(i)+" sise: " +arr.size());
        if("2".equals(arr.get(i))){
            arr.remove("2");
        }
    }
    System.out.println("size : "+arr.size());

Result:

1 sise: 4
2 sise: 4
4 sise: 3
size : 3

Indeed, as you can see, it does not affect the traversal of the for loop and does not produce any exceptions.

  • Delete in the foreach loop
    for (String str: arr) {
           System.out.println(str);
           if("2".equals(str)){
               arr.remove("2");
           }
    }
    System.out.println("size : "+arr.size());   

Result:

1
Exception in thread "main" java.util.ConcurrentModificationException
2
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.fei.java.Test.main(Foo.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ......

As you can see, an exception of type java.util.Concurrent ModificationException is reported here.

  • Delete in the Iterator loop
        Iterator<String> iterator = arr.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next()+" size : "+arr.size());
            if("2".equals(iterator.next())){
//                iterator.remove();
                arr.remove("2");
            }
        }

Result:

1 size : 4
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.fei.java.Test.main(Foo.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ......

The same error message as above is throwing java.util.ConcurrentModificationException exceptions
What's the matter? Let's track the source code and find the answer according to the source code.

Source Code Analysis Iterator

Let's analyze the source code used by Iterator. In fact, the implementation principle of foreach is also Iterator.

This function of IDEA is still very good, shortcut key: Ctrl+Alt+U can quickly generate the class diagram structure of the current class. In fact, putting this graph in place does not make much sense, so we should strengthen the understanding of collection classes. The important thing is to implement Iterator yourself in ArrayList.

When traversing ArrayList in the form of Iterator above, the iterator.next() method is called:

        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;//Cursor add 1
            return (E) elementData[lastRet = i];//lastRet records the value before the cursor is added 1
        }

Before each call to the specific function of this method, a check ForComodification () is made to make a modification that is worth checking:

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

Among them, the modCount value records the number of times that ArrayList has been modified (adding and deleting elements will be modCount++), while the expectedModCount value is initialized when the terator is acquired, and the initial value is the modCount value before traversal. Then, when the arr.remove("2") operation is performed during traversal, the java. util. Concurrent ModificationException exception is thrown here because the modCount++ operation in the remove method results in inconsistency between the two values. Let's look at the remove method of arr.remove("2"). Note that the remove method here is the remove method of ArrayList, and the remove method in Iterator is different.

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    //Finally, this method is called.
    private void fastRemove(int index) {
        modCount++;//As you can see, the modCount++ operation was actually performed
        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
    }

It is also worth noting that in the next() method of Iterator, this anomaly may occur when cursor value is compared with element length. The reason is the same, because of the anomaly caused by the change of length.

How to delete elements correctly when using Iterator traversal

But we can actually delete elements during Iterator traversal, except that we use the remove method in Iterator. In fact, it is not difficult to figure out why this remote method can delete elements normally, then cursor value must be corrected in this method.

  • Using remote method in Iterator
Iterator<String> iterator = arr.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next()+" size : "+arr.size());
            if("2".equals(iterator.next())){
                   iterator.remove();
            }
        }
        for (String str: arr) {
            System.out.println(str);
        }
        System.out.println("size : "+arr.size());

Operation results:

1 size : 4
3 size : 3
1
3
4
size : 3

This time, there is no error and the elements are deleted. Let's look at the remove method.

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //Really delete elements, modCount++ operation in this operation
                ArrayList.this.remove(lastRet);
                cursor = lastRet;//The cursor value is reset to the previous value (refer to the comments in next)
                lastRet = -1;
                expectedModCount = modCount;//Reset expectedModCount value
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

This remove method does two operations after continuing the deletion operation to ensure that the element is deleted correctly:
(1) Set cursor value to the previous cursor value (refer to next method)
(2) Set the expectedModCount value to the new modCount value

Posted by Eugene on Mon, 07 Jan 2019 22:18:09 -0800