This is the first day of my participation in the August Challenge. For details, see:August is more challenging

From today also officially open JDK principle analysis pit, in fact, the purpose of writing source analysis is no longer as before to understand the principle, more important is to see their coding style to further experience their design ideas. Look at the source code before their own implementation of a rematch may have different harvest!

1. Summary of structure

First of all, we need to get a sense of what an ArrayList looks like in terms of its structure.

1. The inheritance

This class inherits from AbstractList

2. Implement

This class implements many interfaces, as follows:

  1. First of all, this class is a List and naturally there’s a List interface
  2. Then because this class needs RandomAccess, the so-called RandomAccess is any access with a subscript, so the implementation of RandomAccess
  3. And then there are the two interfaces that the collections framework will definitely implement Cloneable, Serializable and we’ll talk more about serialization in a second

3. Main fields

    // The default size is 10
    private static final int DEFAULT_CAPACITY = 10;
    / / an empty array
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // The default is an empty array. This is what the constructor will call later in the add method
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // To store elements in the ArrayList. Note that the modifier is a transient
    transient Object[] elementData; 
    / / size
    private int size;
Copy the code

4. Main methods

The following methods are numbered to indicate overloaded methods

  1. ctor-3
  2. get
  3. set
  4. add-2
  5. remove-2
  6. clear
  7. addAll
  8. write/readObject
  9. Fast – fail mechanism
  10. subList
  11. iterator
  12. forEach
  13. sort
  14. removeIf

2. Analysis of construction methods

1. Constructors with no parameters

The only operation is to set elementData to DEFAULTCAPACITY_EMPTY_ELEMENTDATA, which is an empty array.

// The constructor that takes no arguments, passing an empty array, will create an array of size 10, as shown in add
    public ArrayList(a) {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
Copy the code

2. Pass in the construction of the array size

So this is new an array, and if the array size is zero, it’s assigned EMPTY_ELEMENTDATA

// Create a new underlying array with the arguments passed in
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); }}Copy the code

3. Pass the Collection interface

If the size of the interface is 0, then pass EMPTY_ELEMENTDATA as shown in the above method

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if((size = elementData.length) ! =0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // There is a bug in the JDK: an array of type Object does not necessarily hold an Object of type Object, and may throw an exception
            // An array of type Object might point to an array of its subclasses
            if(elementData.getClass() ! = Object[].class)// This operation is to first new the array and then call system.arrayCopy to copy the value. That's creating a new array
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // If an empty array is passed in, initialize with an empty array
            this.elementData = EMPTY_ELEMENTDATA; }}Copy the code

But notice that there’s a JDK bug here which is that an array of type Object doesn’t necessarily hold an Object of type Object, it throws an exception, mainly because an array of type Object might point to an array of its subclass, An error will be reported for storing objects of type Object. To test this bug, I wrote a few lines of code to test it out. This test is a failure, and that’s why it exists.

A typical example of this would be if we were to create a list of strings and then call toArray and find that it returns a string[] and of course we can’t store any elements anywhere.

class A{}class B extends A {}public class JDKBug {

    @Test
    public void test1(a) {
        B[] arrB = new B[10];
        A[] arrA = arrB;
        arrA[0] =newA(); }}Copy the code

3. Analysis of modification methods

1. Set method

This method is also very simple, first to determine the range, and then directly update the subscript.

    // Set the new value to return the old value
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
Copy the code

2. Add(E E) method

So this method first calls ensureCapacityInternal() which determines if the current elementData is equal to DEFAULTCAPACITY_EMPTY_ELEMENTDATA and if so, I’m going to set the size of the array to 10 and then I’m going to expand it, and this just explains why the List with no parameters is 10, and the method I’m going to expand it is called ensureExplicitCapacity and one of the things I’m going to do is expand it if the user specifies a size larger than the current size, Arraycopy method is implemented by creating a new array, then calling System. arrayCopy to copy the array, and finally returning the new array.

    public boolean add(E e) {
        // When calling no-argument constructs, set the size to 10
        ensureCapacityInternal(size + 1);  // Increments modCount        
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        // If the current array is empty by default, set it to the minimum value in 10 and size+1
        // The size of the new array is 10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // If the specified minimum capacity is greater than the specified minimum capacity, the specified minimum capacity prevails. Otherwise, the value is 10
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 1.5 times increase
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
Copy the code

3. Add(int index, E E

This method is relatively simple. It’s basically the same as above, except that when you put the element in the end, you do a different thing. Instead, you use System.arrayCopy to copy from itself to itself, in order to overwrite the element. Notice the rule that most of the operations that involve subscripts are not handwritten for loops but copying and overwriting things like that. It’s kind of a trick.

public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount
        / / cover
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
Copy the code

4. remove(int index)

Again, there’s a copy-overwrite technique here. One thing to note, however, is that unused nodes need to manually trigger the GC, which is an example the authors use in Efftive Java.

public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        / / cover
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

Copy the code

5. remove(E e)

So this is obviously going to determine if e is null. If it’s null, it’s going to compare it to equals. Otherwise, it’s going to call equals and do the copy override.

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    / / cover
                    fastRemove(index);
                    return true; }}else {
            for (int index = 0; index < size; index++)
                // Call equals
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true; }}return false;
    }
Copy the code

6. clear()

One thing this method does is set all the references in the array to NULL for GC purposes. Instead of just setting size to 0.

// Gc all nodes
    public void clear(a) {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
Copy the code

7. addAll(Collection e)

There’s nothing to say about this except that I’m going to turn the array and copy it

    // All methods that involve the Collection interface convert the interface to an array and operate on the array
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        returnnumNew ! =0;
    }
Copy the code

4. Access method analysis

1. get

Access the array index directly.

    // There's nothing to say
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
Copy the code

2. subList

The interesting implementation of this method is that instead of intercepting a new List and returning it, it has an inner class of subList inside the class, which then records the beginning and end subscripts of subList, and returns the subList object. So you might be wondering if the subList that’s returned is not a List, but the subList here is inherited from AbstractList so it’s still correct.

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this.0, fromIndex, toIndex);
    }
    // subList returns an instance of a position tag, that is, a number of flags are placed on the original array, without modifying or copying the new space
private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;
        // other functions .....
     }

Copy the code

5. Tools

1. write/readObject

I noted earlier when I introduced the data field that elementData is a transition variable, which means that it will be ignored during automatic serialization.

Then we found the write/readObject methods in the source code, which are used to serialize each element in elementData, that is, to serialize and deserialize the field manually. Isn’t that unnecessary?

If you want to serialize the fields of the ArrayList (i.e., elementData), why do you want to decorate elementData with transient?

Recall the automatic expansion mechanism of ArrayList. The elementData array acts as a container. When the container is insufficient, the capacity of the container is expanded, but the capacity of the container is usually greater than or equal to the number of elements in the ArrayList.

For example, now that there are actually eight elements, the size of the elementData array might be 8X1.5 =12. If you serialize the elementData array directly, you would waste the space of four elements, especially if the number of elements is very large, which is very unprofitable.

So the designers of ArrayList designed elementData to be transient, then serialized it manually in the writeObject method, and only serialized those elements that were actually stored, not the entire array.

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
        if(modCount ! = expectedModCount) {throw newConcurrentModificationException(); }}Copy the code

2. fast-fail

The so-called fast-fail is that we are not allowed to call the methods of the Collection interface to modify the container during the iterator traversal, otherwise an exception will be thrown. The mechanism of this implementation is to maintain two variables in the iterator, They are modCount and expectedModCount respectively. Because the methods of the Collection interface will modCount++ every time the modification operation is performed, an exception will be thrown if they are detected to be unequal in the iterator.

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;
        
        final void checkForComodification(a) {
            if(modCount ! = expectedModCount)throw newConcurrentModificationException(); }}Copy the code

3. forEach

This is a functional programming method. Take a look at its argument forEach(Consumer
Action) and it’s interesting that the accept is a functional interface. We have a callback to the Consumer’s accept so we only need to pass in a function interface to handle each element.

    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            / / callback
            action.accept(elementData[i]);
        }
        if(modCount ! = expectedModCount) {throw newConcurrentModificationException(); }}Copy the code

I wrote a test code, but this method is not commonly used, the main reason is that Collection can generate its own Stream object, and then call the above method. Let me mention it here.

public class ArrayListTest {

    @Test
    public void foreach(a) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(2);
        list.add(1);
        list.add(4);
        list.add(6);
        list.forEach(System.out::print);  // Print each element.}}Copy the code

4. sort

The underlying call to the arrays.sort method doesn’t make any sense.

public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if(modCount ! = expectedModCount) {throw new ConcurrentModificationException();
        }
        modCount++;
    }
Copy the code

5. removeIf

This is similar to forEach, except that the callback is written.

6. Compare with Vector

This is basically an introduction to the important methods and properties of ArrayList, and we have a better understanding of its underlying implementation and data structure. And then we have arrayLists and we have an old Vector that looks a lot like ArrayLists. Because you’ll find that the inheritance and implementation interfaces are the same. However, there are some differences, which are described below.

  1. Almost all methods in a Vector are synchronized methods, so it is a thread-safe ArrayList

  2. The constructor is different. There are no two special constants in the property, so its constructor directly initializes an array of size 10. And then he has four constructions.

  3. Different interfaces are traversed. He still has an iterator, but his previous method of traversing was the Enumeration interface, getting the Enumeration from elements and then using hasMoreElements and nextElement to get the element.

  4. Some functional programming methods are missing.