ArrayList

ArrayList is the go‑to dynamic array implementation in Java’s collection framework.
It lives in java.util, extends AbstractList, and implements List, RandomAccess, Cloneable, and Serializable.
Unlike a fixed‑size array, an ArrayList grows automatically, so developers rarely need to pre‑allocate a specific capacity.


  • Underlying storage – a contiguous Object[] array.
  • Random access – retrieving an element by index is O(1).
  • Nulls & duplicates – both are allowed.
  • Order preservation – elements remain in insertion order, making it an ordered collection.
  • Thread safety – not synchronized; external coordination is required in concurrent scenarios.

Basic Usage

Creation & Initialization

// Empty list; capacity is allocated lazily on first add
List<String> list = new ArrayList<>();

// Pre‑allocate to avoid repeated resizing
List<String> list2 = new ArrayList<>(100);

// Wrap an existing collection
List<String> list3 = new ArrayList<>(Arrays.asList("A", "B", "C"));

CRUD Operations

list.add("Java");          // Append at the end
list.add(0, "Hello");      // Insert at a specific index
String s = list.get(0);    // Retrieve by index
list.set(0, "World");      // Replace element at index
list.remove(0);            // Remove by index
list.remove("Java");       // Remove by value
int size = list.size();    // Current number of elements

Traversal Techniques

// Classic for loop
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// Enhanced for loop (recommended)
for (String item : list) {
    System.out.println(item);
}

// forEach with a lambda
list.forEach(System.out::println);

Expansion Strategy

The resizing algorithm is a key part of ArrayList’s design:

  1. Lazy allocation – the internal array starts empty; the first add triggers allocation of the default capacity (10).
  2. Trigger point – when the element count exceeds the current capacity, a resize occurs.
  3. Growth factor – new capacity = old capacity + (old capacity >> 1) (i.e., 1.5×).
  4. Copying cost – Arrays.copyOf creates a new array and copies existing elements, incurring a performance hit.

Because resizing involves copying, it’s wise to specify an initial capacity when you can estimate the final size. For example, loading 10,000 records should use new ArrayList<>(10_000) to reduce the number of resizes and improve throughput.


ArrayList vs. LinkedList

Feature ArrayList LinkedList
Structure Dynamic array Doubly‑linked list
Random access O(1) O(n)
Append at end Amortized O(1) O(1)
Insert/Remove in middle O(n) Locate O(n), operation O(1)
Memory overhead Continuous block, possible unused space Each node stores two pointers

In practice, ArrayList dominates because of its cache friendliness and low per‑element overhead. LinkedList shines only when frequent insertions/removals at the ends are required and the list is large enough that the pointer overhead is acceptable.


Common Pitfalls & Best Practices

  1. ConcurrentModificationException – modifying a list while iterating with a for‑each loop triggers a fail‑fast exception. Use an Iterator and its remove() method, or list.removeIf(predicate).

  2. Autoboxing ambiguity – list.remove(1) on a List<Integer> removes the element at index 1, not the value 1. To delete by value, use list.remove(Integer.valueOf(1)).

  3. subList is a view – subList() returns a view backed by the original list. Changes to the view affect the parent list and vice versa.

  4. Thread safety – wrap with Collections.synchronizedList(new ArrayList<>()) or use CopyOnWriteArrayList for read‑heavy, write‑light scenarios.

  5. Arrays.asList limitations – the list returned by Arrays.asList() has a fixed size; attempts to add or remove elements throw UnsupportedOperationException. Convert to a mutable list first if mutation is needed.


Takeaway

ArrayList’s simplicity, efficient random access, and automatic resizing make it the default choice for most Java collections tasks. By understanding its underlying array mechanics, the 1.5× growth policy, and the subtle quirks that can trip developers, you can write code that is both performant and robust. When your workload involves frequent traversal or indexed access, ArrayList is almost always the optimal solution.