Skip to content
C Codeloom
Java

Java Collections Framework Cheatsheet

A pragmatic tour of Java's collection interfaces and implementations, with guidance on choosing between List, Set, Map, and Queue variants in real applications.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • The shape of the Collections Framework hierarchy
  • When to pick ArrayList vs LinkedList vs ArrayDeque
  • How HashMap, LinkedHashMap, and TreeMap differ
  • Concurrent collection options under contention
  • Common iteration and modification pitfalls

Prerequisites

  • Basic familiarity with Java syntax and generics

What and Why

The Java Collections Framework (JCF) is the set of interfaces and classes in java.util that model groups of objects. It is one of the most heavily used parts of the standard library, and choosing the right collection often has bigger performance impact than micro-optimizing your code.

The framework gives you a uniform API: every collection supports iteration, size queries, and bulk operations. That uniformity is the productivity win. The cost is that you have to know which implementation matches your access pattern.

Mental Model

Think of the framework as three trees rooted in Iterable. Collection branches into List, Set, and Queue. Map lives on its own because it stores key-value pairs rather than elements. Each interface has several concrete classes optimized for a specific tradeoff between memory layout, ordering, and concurrency.

Iterable
|
Collection                    Map
 |-- List                      |-- HashMap
 |     |-- ArrayList           |-- LinkedHashMap
 |     |-- LinkedList          |-- TreeMap
 |-- Set                       |-- ConcurrentHashMap
 |     |-- HashSet
 |     |-- LinkedHashSet
 |     |-- TreeSet
 |-- Queue / Deque
       |-- ArrayDeque
       |-- PriorityQueue
       |-- LinkedBlockingQueue
Java Collections Framework hierarchy (subset)

Hands-on Example

Here is a small program that demonstrates the most useful collections.

import java.util.*;
import java.util.concurrent.*;

public class CollectionsDemo {
    public static void main(String[] args) {
        // Random access, dynamic size
        List<String> names = new ArrayList<>(List.of("Ada", "Bob", "Cleo"));
        names.add("Dan");
        System.out.println(names.get(2)); // Cleo

        // Unique elements, no order
        Set<Integer> ids = new HashSet<>();
        ids.add(42); ids.add(42); ids.add(7);
        System.out.println(ids.size()); // 2

        // Sorted by key
        NavigableMap<String, Integer> scores = new TreeMap<>();
        scores.put("Ada", 90);
        scores.put("Bob", 75);
        System.out.println(scores.firstEntry()); // Ada=90

        // FIFO queue
        Deque<String> q = new ArrayDeque<>();
        q.offer("first"); q.offer("second");
        System.out.println(q.poll()); // first

        // Thread-safe map
        ConcurrentMap<String, Integer> hits = new ConcurrentHashMap<>();
        hits.merge("home", 1, Integer::sum);
    }
}

A few practical heuristics:

  • ArrayList is the default List. Use LinkedList only when you really need O(1) insertion at both ends, and prefer ArrayDeque for that.
  • HashMap is the default Map. Use LinkedHashMap when you need insertion-order iteration (great for LRU caches via removeEldestEntry).
  • TreeMap and TreeSet give you sorted views and range queries via subMap, headSet, tailMap.
  • For concurrency, prefer ConcurrentHashMap and CopyOnWriteArrayList over Collections.synchronizedXxx wrappers.

Common Pitfalls

  • Mutating during iteration: modifying a non-concurrent collection while iterating with an enhanced for-loop throws ConcurrentModificationException. Use Iterator.remove() or removeIf instead.
  • Arrays.asList is fixed size: it returns a list backed by the original array. add and remove throw UnsupportedOperationException. Wrap it in new ArrayList<>(...) if you need to grow it.
  • Hash code contracts: putting mutable objects into a HashSet and then mutating them breaks lookups silently. Override equals and hashCode consistently, and keep keys effectively immutable.
  • Null keys and values: HashMap allows them, TreeMap and ConcurrentHashMap do not.

Practical Tips

Size your ArrayList and HashMap up front when you know the cardinality. The default load factor is 0.75, so new HashMap<>(expectedSize * 4 / 3 + 1) avoids the first resize.

Prefer List.of, Set.of, and Map.of for immutable literals introduced in Java 9. They are compact and reject nulls early.

For grouping operations, lean on Stream.collect(Collectors.groupingBy(...)) instead of hand-rolled loops. It composes well with counting, summingInt, and mapping.

When profiling memory, remember that HashMap entries carry significant overhead per node (key, value, hash, next pointer). For small fixed maps, an array of records can be cheaper.

Wrap-up

The JCF rewards thinking about access patterns before you reach for a class name. Start with ArrayList, HashMap, and ArrayDeque. Reach for ordered, sorted, or concurrent variants only when the requirement is real. Once these defaults are muscle memory, you can read most Java code at a glance and predict its performance characteristics.