Learning goals
By the end of this chapter you should be able to:
- Navigate the
CollectionandMaphierarchies and name the core interfaces - Decide when to use List, Set, Queue, or Map for a given problem
- Declare collections against interfaces, not concrete classes
- Recognize ordered vs sorted vs insertion-order — three different ideas
The Java Collections Framework is the standard toolkit for grouping values in memory: lists, sets, queues, and maps. This chapter is the decision map before you dive into each structure in detail.
Two hierarchies
Java splits “many values” into two trees:
Iterable
└── Collection
├── List (ordered, duplicates OK)
├── Set (no duplicate equals-elements)
└── Queue (FIFO / priority / deque variants)
Map (separate — keys to values, not a Collection)
Collection holds elements. Map holds entries keyed uniquely. You cannot assign a Map to a Collection reference.
List — ordered sequence
Use a List when order matters and duplicates are allowed.
List<String> tasks = new ArrayList<>();
tasks.add("deploy");
tasks.add("deploy"); // duplicate OK
String first = tasks.get(0);
Default mutable implementation: ArrayList. Indexed access is fast; middle insertions shift elements.
Immutable snapshots:
List<String> frozen = List.of("a", "b", "c");
List<String> copy = List.copyOf(someMutableList);
Set — unique membership
Use a Set when you care about presence, not how many times or in what order (unless you pick an ordered set).
Set<String> seen = new HashSet<>();
seen.add("user-42");
seen.add("user-42"); // ignored — set unchanged
boolean loggedIn = seen.contains("user-42");
HashSet — average O(1) add/contains, undefined iteration order.
LinkedHashSet — insertion-order iteration.
TreeSet — sorted order, O(log n), needs Comparable or Comparator.
Queue and Deque — processing order
Use a Queue when elements are processed in a defined order (usually FIFO).
Queue<String> fifo = new ArrayDeque<>();
fifo.offer("job-1");
fifo.offer("job-2");
String next = fifo.poll(); // "job-1"
Deque adds both-end operations — stack (push/pop) or double-ended queue.
PriorityQueue is not FIFO — always returns the smallest (or comparator-first) element.
Map — key to value
Use a Map when you look up values by a key and each key appears once.
Map<String, Integer> scores = new HashMap<>();
scores.put("alice", 95);
scores.putIfAbsent("bob", 80);
int alice = scores.get("alice");
HashMap — default mutable map, one null key allowed.
LinkedHashMap — predictable iteration or LRU-style access order.
TreeMap — sorted keys, navigable ranges.
ConcurrentHashMap — thread-safe without locking the entire map.
Decision guide
| Need | Choose |
|---|---|
| Index by position, allow duplicates | List |
| Fast “is X present?” | Set |
| Stable iteration order of inserts | LinkedHashSet / LinkedHashMap |
| Sorted keys or range queries | TreeSet / TreeMap |
| FIFO job processing | Queue / ArrayDeque |
| Smallest item next | PriorityQueue |
| Key → value lookup | Map |
| Many threads reading/writing | ConcurrentHashMap, concurrent queues |
| Fixed constants published to callers | List.of, Set.of, Map.of, Map.copyOf |
When two structures fit, prefer the simpler contract: a Set over a Map with dummy values.
Program to interfaces
// Good — swap ArrayList for LinkedList in one place
List<User> users = new ArrayList<>();
// Avoid unless you need Deque-specific methods
ArrayDeque<Task> tasks = new ArrayDeque<>();
Return List, Set, Map from public methods. Hide concrete types unless callers need implementation-specific performance (rare).
equals, hashCode, and comparators
HashSet and HashMap require consistent equals and hashCode on keys/elements.
record User(String id, String name) {
@Override public boolean equals(Object o) { /* id only */ }
@Override public int hashCode() { return Objects.hash(id); }
}
TreeSet / TreeMap need Comparable or a Comparator — equality follows comparison, not equals.
Breaking these contracts causes “lost” entries and impossible-to-debug set/map behavior.
Common mistakes
- Using
List.containsfor frequent lookups — O(n); useSetorMapkey set. - Confusing sorted with insertion-ordered —
HashSetis neither;TreeSetis sorted. - Mutating keys while inside a
HashMap— if hashCode changes, the entry is stranded. - Returning internal mutable collections — callers can break your invariants; return copies or unmodifiable views.
- Choosing
Vector/Hashtable— legacy synchronized types; useArrayList+ConcurrentHashMappatterns instead.
Practice checkpoint
-
Track unique visitor IDs per day with fast membership checks — which type? Answer:
Set<String>(HashSetorConcurrentHashMap-backed set if concurrent). -
Rank leaderboard top 10 by score, repeatedly poll highest — which type? Answer:
PriorityQueue(orTreeMapif you also need lookup by name). -
Cache config key → value with LRU eviction — which map? Answer: access-order
LinkedHashMapwith size cap, orCaffeine/similar in production. -
Why declare
Map<K,V> m = new HashMap<>()notHashMapon the left? Answer: flexibility to change implementation; clearer contract to callers.
Interview angles
- Complexity:
ArrayListget O(1), add end amortized O(1), middle insert O(n);HashMapaverage O(1). - Fail-fast iterators: structural modification during iteration (except via iterator remove) →
ConcurrentModificationException. - Null policy:
HashMapallows one null key;ConcurrentHashMapallows no nulls. - Immutable factories:
List.ofrejects null elements; fixed size.