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