EMZETT.
Login

ArrayList

In short: The most common List implementation in Java — internally a dynamically growing array that automatically resizes as needed.

In more detail: Access by index (get(i)) is very fast (constant time), while inserting/removing in the middle is slow, since subsequent elements have to be shifted. Unlike a classic array, the size doesn’t need to be fixed in advance.

List<String> names = new ArrayList<>();
names.add("Anna");
names.get(0);

In Depth

Internally, an ArrayList holds a normal array as a backing array. If it’s full and another element is to be added, Java automatically creates a new, larger array (typically 1.5 times the old size) and copies all elements over — invisible to the caller, but not free. Anyone who already knows the approximate final size can avoid repeated resizing with the constructor new ArrayList<>(expectedSize).

List<String> names = new ArrayList<>();
names.add("Anna");
names.add("Ben");
names.add(0, "First"); // insert at position 0 - shifts everything after it
 
names.get(0);      // "First" - O(1), very fast
names.remove(0);   // removes "First" - O(n), everything after it moves up
names.contains("Ben"); // true - O(n), searches linearly
 
for (String name : names) { // for-each internally uses an iterator
    System.out.println(name);
}

Important pitfalls: ArrayList is NOT thread-safe (multiple threads must not modify it at the same time, or ConcurrentModificationException or data corruption looms — for that there’s Collections.synchronizedList() or CopyOnWriteArrayList). And: removing elements directly via list.remove() during a running for-each loop throws a ConcurrentModificationException — that instead requires iterator.remove() via an explicit iterator.

Common operations at a glance

List<Integer> numbers = new ArrayList<>(List.of(5, 3, 8));
 
numbers.set(0, 99);          // replaces the element at index 0
numbers.indexOf(8);          // 2 - first occurrence, -1 if absent
numbers.isEmpty();           // false
numbers.size();              // 3
numbers.clear();             // clears the list completely
Collections.sort(numbers);   // sorts in place (see also Sorting)

ArrayList vs. LinkedList

The second common List implementation is LinkedList, internally a doubly linked list. With ArrayList, random access (get(i)) is very fast (O(1)), while inserting at the start/middle is slow (O(n), since elements have to be shifted). With LinkedList it’s the reverse: inserting at the start/end is O(1) (just relinking pointers), but get(i) has to walk the chain from the start (O(n)). In practice, ArrayList is almost always the better default choice — modern CPUs favour the contiguous memory area of an array (cache locality), LinkedList is only worthwhile for very frequent inserting/removing at both ends with no random access.

Generics and autoboxing

ArrayList<Integer> doesn’t internally store primitive int values, but Integer objects — every element is automatically “boxed” on insert (int → Integer) and “unboxed” again on read. For very large lists of primitive numbers, this noticeably costs memory and performance compared to a raw int[] array — one reason why performance-critical code sometimes deliberately relies on arrays instead of collections.

See also: List, LinkedList, Arrays, Iterator, Generics