When reading the ArrayList source code, I found that the array elementData that holds the elements uses the transient modifier, which states that the array will not be serialized by default.

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    // Android-note: Also accessed from java.util.Collections
    transient Object[] elementData; // non-private to simplify nested class access
Copy the code

After serialization, the data stored in the array of elements in the ArrayList is completely lost. Digging into the code, it turns out that no, ArrayList provides two methods for serialization and deserialization, readObject and writeObject:

    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    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 new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
Copy the code

ArrayList calls writeObject when serialized, and writes size and Element directly to ObjectOutputStream. Call readObject when deserializing, get size and Element from ObjectInputStream, and then revert to elementData.

### Why not serialize directly with elementData instead of the way above?

The reason for this is that elementData is a cache array with a default size of 10. Add to the ArrayList and expand the ArrayList when space is running out. Usually the multiple of expansion is 1.5 times.

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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

Therefore, the elementData array reserves some capacity, and when the capacity is insufficient, some space may not have the actual storage elements. When implementing serialization in this way, you can ensure that only the actual stored elements are serialized, rather than the entire array, saving space and time.

WriteObject/readObject/writeObject/writeObject/writeObject Although writeObject and readObject are called by external classes, these are actually two private methods. And they don’t exist in java.lang.Object, nor are they declared in Serializable. So how does ObjectOutputStream use them?

Serialization requires the writeObject() of ObjectOutputStream to convert the object into a byte stream and output it. The writeObject() method will reflect the writeObject() of the passed object if it has writeObject() to achieve serialization. Deserialization uses ObjectInputStream’s readObject() method, which works similarly.

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);
Copy the code

Let’s take ObjectInputStream as an example to review the call process. If you are interested, you can read the source code

First of all, ReadObject () -> Object obj = readObject0(false) -> readObject0 -> return is called when deserializing checkResolve(readOrdinaryObject(unshared)) -> readOrdinaryObject -> readSerialData(obj, desc);

ReadSerialData will then call slotDesc. InvokeReadObject (obj, this) here call ObjectStreamClass invokeReadObject (Object obj. Invoke (obj, new Object[]{in});

This is obviously a method call made through reflection, so what is readObjectMethod? readObjectMethod = getPrivateMethod(cl, “readObject”,new Class
[] { ObjectInputStream.class },Void.TYPE); writeObjectMethod = getPrivateMethod(cl, “writeObject”,new Class
[] { ObjectOutputStream.class },Void.TYPE); The writeObjectMethod getPrivateMethod method is as follows:

/** * Returns non-static private method with given signature defined by given * class, or null if none found. Access checks are disabled on the * returned method (if any). */ private static Method getPrivateMethod(Class<? > cl, String name, Class<? >[] argTypes, Class<? > returnType) { try { Method meth = cl.getDeclaredMethod(name, argTypes); meth.setAccessible(true); int mods = meth.getModifiers(); return ((meth.getReturnType() == returnType) && ((mods & Modifier.STATIC) == 0) && ((mods & Modifier.PRIVATE) ! = 0))? meth : null; } catch (NoSuchMethodException ex) { return null; }}Copy the code

At this point we get the general idea that ObjectInputStream will call the private readObject method via reflection. Implement deserialization.

CopyOnWriteArrayList Private TRANSIENT Volatile Object[] elements; CopyOnWriteArrayList private volatile Object[] elements; HashMap transient Node

[] table; HashSet private transient HashMap

map;
,object>
,v>

The reason for this is to ensure that only the elements that are actually stored are serialized, rather than the entire array, saving space and time.

#### Reference article: How are readObject, writeObject, and readResolve called when JAVA object streams are serialized