Collections Framework
List, Set, Map, Queue — implementations, trade-offs, and iteration patterns
The Java Collections Framework provides a unified architecture for storing and manipulating groups of objects. Choosing the right data structure has a direct impact on time complexity and memory usage. Every Java developer must know the internal workings and trade-offs of the core implementations.
Key Points
- ArrayList: backed by an array, O(1) random access, O(n) insert/delete in middle, amortised O(1) add-to-end
- LinkedList: doubly-linked list, O(1) insert/delete at known position, O(n) random access — rarely outperforms ArrayList in practice due to cache misses
- HashSet / HashMap: backed by a hash table, O(1) average get/put/contains — degrades to O(n) on hash collisions
- LinkedHashSet / LinkedHashMap: maintain insertion order via a doubly-linked list — O(1) operations with ordering
- TreeSet / TreeMap: backed by a Red-Black Tree, O(log n) operations, elements kept sorted — implements SortedSet/SortedMap
- ArrayDeque: double-ended queue, O(1) push/pop/peek both ends — prefer over Stack and LinkedList as a queue
- PriorityQueue: min-heap, O(log n) add/poll, O(1) peek — does not guarantee ordering on iteration
- ConcurrentHashMap (Java 8+): lock-striping per bucket, thread-safe without locking entire map — use over Hashtable
- Fail-fast iterators throw ConcurrentModificationException if collection modified during iteration; fail-safe (CopyOnWriteArrayList, concurrent collections) iterate over a snapshot
| Collection | Get | Add | Remove | Contains | Ordered? | Thread-safe? |
|---|---|---|---|---|---|---|
| ArrayList | O(1) | O(1)* | O(n) | O(n) | Insertion | No |
| LinkedList | O(n) | O(1) | O(1)** | O(n) | Insertion | No |
| HashSet | — | O(1) | O(1) | O(1) | No | No |
| LinkedHashSet | — | O(1) | O(1) | O(1) | Insertion | No |
| TreeSet | — | O(log n) | O(log n) | O(log n) | Sorted | No |
| HashMap | O(1) | O(1) | O(1) | O(1) | No | No |
| TreeMap | O(log n) | O(log n) | O(log n) | O(log n) | Sorted | No |
| PriorityQueue | O(1)*** | O(log n) | O(log n) | O(n) | Heap order | No |
| ConcurrentHashMap | O(1) | O(1) | O(1) | O(1) | No | Yes |
Common Collections patterns: merge for frequency maps, TreeMap range queries, ArrayDeque dual role, CopyOnWriteArrayList
// HashMap — custom key requires equals + hashCode
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
freq.merge(word, 1, Integer::sum); // Java 8 merge
}
// TreeMap — sorted by key, get floor/ceiling/range
TreeMap<Integer, String> tm = new TreeMap<>();
tm.put(1, "a"); tm.put(3, "c"); tm.put(5, "e");
tm.subMap(1, true, 4, false).forEach((k, v) -> ...); // keys 1..3
// ArrayDeque as stack and queue
Deque<Integer> deque = new ArrayDeque<>();
deque.push(1); // stack: addFirst
deque.offer(2); // queue: addLast
deque.pop(); // stack: removeFirst
deque.poll(); // queue: removeFirst
// CopyOnWriteArrayList — safe iteration without locks
List<String> safe = new CopyOnWriteArrayList<>(list);
for (String s : safe) { // iterates snapshot — no CME
safe.add("new"); // writes create a new array copy
}
// Java 9+ immutable collections
List<Integer> immutable = List.of(1, 2, 3); // UnsupportedOperationException on mutation
Map<String, Integer> map = Map.of("a", 1, "b", 2);Real-World Example
HashMap in Java 8+ converts a bucket's linked list to a Red-Black Tree when a bucket exceeds 8 entries (TREEIFY_THRESHOLD) — this prevents worst-case O(n) performance from hash collision attacks. Default HashMap capacity is 16 with load factor 0.75; when 75% full it resizes (doubles) and rehashes — pre-size with new HashMap<>(expectedSize * 4 / 3 + 1) for large maps to avoid resizes.