Do you really understand the source code of ArrayList

Posted by CountryGirl on Wed, 24 Nov 2021 09:00:00 +0100

problem

I believe many young friends have been asked about the relevant source code in the jdk during the interview. Recently, they have been asked about the source code of ArrayList. They come up to ask whether they have seen the source code of ArrayList and how to remove the elements in ArrayList during traversal, What kind of exception will be thrown by an ordinary loop, and why will the iterator not report an exception,

answer:

  • Ordinary for loop compilation will actually call Itr's next to get elements and throw ConCurrentModificationException

  • There are two ways:

  • Method 1: through the iterator, there is an internal class Itr in ArrayList that implements the iterator interface. You can remove elements through the remove method of Itr object

  • Method 2: the removeIf method connected to the Collection removes elements

analysis

From the compiled file, we can see that the List.iterator method is called during the for loop


/** From the source code, you can see that the iterator method puts back an Itr object
 *Itr Object is an internal class in ArrayList that implements the iterator interface
 */ ArrayList.iterator 
public Iterator<E> iterator() {
    return new Itr();
}
	

Private internal class Itr source code, three important member variables,
cusor: the next element index starts with 0 by default
lasRet: index position of the last element
expectedModCount: the number of times the collection was modified when the iterator was created
It is precisely because the common for loop is actually the built-in object Itr of the calling ArrayList to traverse the data, and the next method of Itr sets the modified check. When the remove method is invoked in the for loop, the actual modification times are inconsistent with the actual number of modifications, thus determining that the concurrent modification of the loop throws an exception.

private class Itr implements Iterator<E> {
        //Index of the next element
        int cursor;       // index of next element to return
        //Index of the last element
        int lastRet = -1; // index of last element returned; -1 if no such
        //The expected number of modifications, that is, the number of array modifications when the iterator is created.
        //modcount refers to the actual number of modifications. This value will be incremented by one each time it is modified or added
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            //Check whether there is any modification - ***** key points******
            checkForComodification();
            //Determine whether all of the next bit positions exceed size
            int i = cursor;
            //If it exceeds, it will directly prompt that there is no such element
            if (i >= size)
                throw new NoSuchElementException();
            //Otherwise, take out the object array
            Object[] elementData = ArrayList.this.elementData;
            //If the next one exceeds the length of the array, it indicates that it has been modified concurrently, and an exception is thrown
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //No, continue to record the next position
            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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
        /**
        *If there is any modification, the currentmodifadexception ----- -- > quick failure mechanism (fail fast) will be thrown directly
        *Compare the expected modification times with the actual modification times. If it is not equal, it indicates that it has been modified, and an exception is thrown directly
        *
        */
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

2. How can I remove or add data to the List in a loop?

Method 1: iterator
The biggest difference from the for loop is that it calls the remove method of the iterator interface, that is, the remove method of the built-in object Itr in ArrayList. You can see the source code. The secret is that this remove method does not make modcut + +,
In addition, the value of modCount is forcibly assigned to expectedpmodCount. In this way, modCount will always be equal to expectedmodeCount, so that checkForComodification will not report exceptions
Test code:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
    String next = iterator.next();
    if (next.equals("b")){
        iterator.remove();
    }
}
System.out.println(list.toString());

iterator.remove() source code:

/**
* The biggest difference from the ArrayList.remove() method is that modCount is not incremented when the element is removed
*This is also the key
*/
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
        //Because it has not been modified at this time, expectedModCount==modCount, no exception will be thrown here
    checkForComodification();

    try {
        //Removes the element at its current location
            ArrayList.this.re move(lastRet);
        cursor = lastRet;
        lastRet = -1;
        //Forced equality
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

Method 2: removeIf
Because ArraryList implements the Collection interface indirectly, you can also use the removeIf method of Colletion. If you find out why iterator's remove() method does not report an error, it is clear from the source code.

Test code:

list.removeIf(fiter->fiter.equals("b"));

Source code: (you can tell at a glance that it's still Iteator.remove())

default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

Topics: Java Interview source code analysis