The Role of modCount in ArrayList

Keywords: Java JDK

There is a member variable modCount in ArrayList, which inherits from AbstractList.

This member variable records the number of modifications to the collection, which add s or remove s its value by 1 each time. What's the use of this?

Let's start with the following test code:

 

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                list.remove(str);
            }
    }
}

Modify the collection elements while traversing the collection using an iterator. Because ArrayList is designed to be asynchronous, exceptions are naturally thrown. But what exception should be thrown to illustrate the problem?

Firstly, we get the iterator for solving ArrayList.

 public Iterator<E> iterator() {
        return new Itr();
    }

When you call list.iterator(), you return an Itr object, which is an internal class in ArrayList.

    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;

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

        @SuppressWarnings("unchecked")
        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];
        }

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

There are three main concerns:

1. The initial value of expectedModCount is modCount.

2. hasNext's criterion is cursor!=size, which means that the current iteration position returns true if it is not the maximum capacity value of the array.

3. Before next and remove operations, check ForComodification is called to check whether expectedModCount and modCount are equal.

If you don't check ForComodification to check that expectedModCount is equal to modCount, the program will report Array Indexed OfBoundsException.

Such exceptions are obviously not supposed to occur (these run-time errors are caused by user's logic errors, our JDK is so high-end, there will be no use errors, we only throw out user-caused errors, which should be considered by the designer), in order to avoid such exceptions, the definition is given. Check. So throwing a Concurrent ModificationException exception is more telling.

Change the test code to read as follows:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                if(str.equals("d")){
                    list.remove(str);
                }else{
                    System.out.println(str);
                }
            }
    }
}

The output is a b c.

Because cursor is 4 and size is 4 when d is deleted. So hasNext returns to true, and the loop ends, so that the following elements are not output.

Why not change hasNext() judgment to cursor <= size ()? But we also have the possibility that add ing () can cause data confusion, and in fact thread security itself does not allow reads to be modified.

This problem is easy to expose when operating on the same set in multi-threaded situations. Even if the problem is changed to a synchronous Vector problem, it still exists and needs to make CopyOnWriteArrayList.

Posted by grevathi_02 on Sun, 21 Jul 2019 01:08:22 -0700