The ArrayList Class
Objective
Understand ArrayList, the default general-purpose List implementation: a resizable array of object references that grows automatically as elements are added, giving constant-time indexed access in exchange for costlier inserts and removals away from the end.
Use Cases
- The default choice for a
Listwhen reads by index dominate over inserts/removes in the middle. - Converting a collection to a plain array to hand off to array-only APIs.
- Pre-sizing the backing array up front when the eventual element count is roughly known, to avoid repeated reallocation.
- Shrinking the backing array's memory footprint after a large batch of removals.
Deep Dive
ArrayList extends AbstractList
javaclass ArrayList<E>ArrayList implements List<E>, plus the marker interfaces RandomAccess, Cloneable, and Serializable. E specifies the element type. Three constructors:
javaArrayList<String> a = new ArrayList<>(); // empty, default capacity
ArrayList<String> b = new ArrayList<>(List.of("x", "y")); // initialized from a collection
ArrayList<String> c = new ArrayList<>(100); // pre-sized to hold 100 without resizingCapacity vs. size
Capacity (the length of the backing array) and size (the element count) are different numbers. Capacity grows automatically, but you can manage it directly:
javaArrayList<Integer> nums = new ArrayList<>();
nums.ensureCapacity(1000); // resize once, up front, before a large batch of adds
// ... add up to 1000 elements without further reallocation ...
nums.trimToSize(); // shrink the backing array down to exactly size()Calling ensureCapacity() before a known-large batch of inserts avoids the cost of several incremental reallocations as the list grows past its current capacity one add at a time.
Watch it happen: add() appending at the end
Every add(E) lands in the next free slot, in arrival order — no hashing, no sorting, just the backing array growing by one:
No collisions, no reordering — index and slot are the same number, which is exactly why get(index) is O(1): it jumps straight there.
toArray(): three overloads
javaObject[] toArray();
<T> T[] toArray(T[] array);
default <T> T[] toArray(IntFunction<T[]> generator); // added in JDK 11The first returns a raw Object[]. The second and third return an array of the actual element type — the third lets you supply the array constructor directly instead of a pre-sized array:
javaArrayList<Integer> al = new ArrayList<>(List.of(1, 2, 3, 4));
Integer[] ia = al.toArray(new Integer[0]);
Integer[] ia2 = al.toArray(Integer[]::new); // JDK 11+, equivalent, no throwaway array literalTrade-offs
Indexed access is O(1), but inserting or removing away from the end is O(n) —
get(index)/set(index, E)read or overwrite a slot directly, whileadd(index, E)/remove(index)shift every following element by one:javaArrayList<String> al = new ArrayList<>(List.of("a", "b", "c", "d")); al.add(1, "x"); // shifts b, c, d one slot rightA
LinkedListinverts this trade-off.A no-arg
ArrayList()doesn't allocate its backing array until the first element is added —size()is0immediately, but no 10-slot array exists yet; the allocation is deferred to the firstadd()call, not the constructor.ArrayListis not synchronized — concurrently modifying it from multiple threads, or structurally modifying it while iterating (other than through the iterator's ownremove()), produces undefined behavior or aConcurrentModificationException:javaArrayList<String> al = new ArrayList<>(List.of("a", "b")); for (String s : al) { al.add("c"); // ConcurrentModificationException on the next iteration }