Collections Framework
List / Set / Map / Queue and mainstream implementations, choosing, iteration pitfalls
The Collections Framework at a Glance
The Java Collections Framework (java.util) consists of a few core interfaces plus their implementations. Understanding the interface semantics and implementation trade-offs is the foundation of writing Java efficiently.
Collection (top)
├── List ordered, allows duplicates, index access
│ ├── ArrayList array-backed, fast random access (use it 90% of the time)
│ └── LinkedList linked-list, fast head/tail insertion
├── Set no duplicates, generally unordered
│ ├── HashSet hash table, O(1) lookup, the most common
│ ├── LinkedHashSet keeps insertion order
│ └── TreeSet red-black tree, auto-sorted
└── Queue queue: FIFO / priority queue / deque
├── ArrayDeque deque, great as both stack and queue
├── LinkedList also implements Queue
└── PriorityQueue binary heap, dequeues by priority
Map (separate hierarchy)
├── HashMap hash table, O(1), the most common
├── LinkedHashMap keeps insertion order (or access order, for LRU)
├── TreeMap red-black tree, sorted by key
└── ConcurrentHashMap concurrency-safe, essential for multithreadingList: ArrayList Is the Workhorse
Use case: 99% of "I need a dynamic array" — order lists, query results, config items. LinkedList only wins in the rare case of frequent head/tail insertion without random access; rarely used day to day.
// ListDemo.java
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> users = new ArrayList<>();
users.add("Alice");
users.add("Bob");
users.add(1, "Carol"); // insert at index 1
System.out.println(users); // [Alice, Carol, Bob]
System.out.println(users.get(0)); // Alice
System.out.println(users.size()); // 3
System.out.println(users.contains("Bob")); // true
System.out.println(users.indexOf("Bob")); // 2
users.remove("Carol");
users.set(0, "ALICE");
System.out.println(users);
// sub-list (a view; modifying it affects the original)
List<String> sub = users.subList(0, 1);
System.out.println(sub);
}
}Set: the Deduplication Trio
Use cases for the three Sets: HashSet (most common, you just need "dedup"); LinkedHashSet (dedup and keep insertion order, for a "unique list"); TreeSet (dedup and auto-sort, for a "leaderboard").
// SetDemo.java
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo {
public static void main(String[] args) {
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("go");
tags.add("java"); // duplicate, not added again
System.out.println(tags); // order not guaranteed
Set<String> ordered = new LinkedHashSet<>();
ordered.add("b"); ordered.add("a"); ordered.add("c");
System.out.println(ordered); // [b, a, c]
Set<Integer> sorted = new TreeSet<>();
sorted.add(3); sorted.add(1); sorted.add(2);
System.out.println(sorted); // [1, 2, 3]
}
}Map: Key-Value Storage
Use cases: look up a user by ID, geolocation by IP, cache a computed result by key, count word frequencies. HashMap is the workhorse; LinkedHashMap for an LRU cache; TreeMap for range queries (subMap / floorKey); ConcurrentHashMap for sharing across threads.
// MapDemo.java
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapDemo {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("apple", 5);
stock.put("pear", 3);
System.out.println(stock.get("apple")); // 5
System.out.println(stock.getOrDefault("melon", 0)); // 0
System.out.println(stock.containsKey("apple")); // true
// put only if absent (avoid overwriting)
stock.putIfAbsent("apple", 999);
System.out.println(stock.get("apple")); // still 5
// the classic word-frequency pattern
Map<String, Integer> freq = new HashMap<>();
for (String w : new String[]{"a", "b", "a", "c", "b", "a"}) {
freq.merge(w, 1, Integer::sum);
}
System.out.println(freq); // {a=3, b=2, c=1}
// iterate
for (Map.Entry<String, Integer> e : stock.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
// TreeMap range query
TreeMap<Integer, String> grades = new TreeMap<>();
grades.put(60, "D"); grades.put(70, "C"); grades.put(80, "B"); grades.put(90, "A");
System.out.println(grades.floorEntry(75)); // largest key <=75: 70=C
System.out.println(grades.headMap(80)); // the submap of keys < 80
}
}Queue and Deque
Use cases: breadth-first search (BFS), task queuing, an undo stack, message buffering. ArrayDeque works as both a queue (FIFO) and a stack (LIFO), with better performance than the Stack class — the workhorse. PriorityQueue dequeues by priority, for "todos sorted by due date" or Dijkstra.
// QueueDemo.java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.PriorityQueue;
public class QueueDemo {
public static void main(String[] args) {
// FIFO queue
Deque<String> q = new ArrayDeque<>();
q.offer("a"); q.offer("b"); q.offer("c");
while (!q.isEmpty()) {
System.out.println(q.poll()); // a b c
}
// stack usage (push/pop)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2); stack.push(3);
while (!stack.isEmpty()) {
System.out.println(stack.pop()); // 3 2 1
}
// priority queue: min-heap by default
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5); pq.offer(1); pq.offer(3);
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 1 3 5
}
}
}Immutable Collections List.of / Map.of (Java 9+)
Use cases: config constants, passing a read-only view, preventing downstream mutation. List.of(), Set.of(), Map.of() return truly immutable collections (add/remove throw); elements cannot be null.
// Immutable.java
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Immutable {
static final List<String> PROFILES = List.of("dev", "staging", "prod");
static final Map<String, Integer> LIMITS = Map.of(
"free", 100,
"pro", 10_000,
"max", Integer.MAX_VALUE
);
public static void main(String[] args) {
System.out.println(PROFILES);
System.out.println(LIMITS);
// PROFILES.add("x"); // throws UnsupportedOperationException
// to make an immutable copy from a mutable collection:
List<Integer> source = new java.util.ArrayList<>(List.of(1, 2, 3));
List<Integer> readonly = List.copyOf(source);
System.out.println(readonly);
}
}Iteration and the ConcurrentModificationException Pitfall
Calling the collection's add/remove during a for-each iteration throws ConcurrentModificationException. To remove while iterating, use the iterator's own remove() method, or use removeIf().
// IterationTrap.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IterationTrap {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ❌ throws ConcurrentModificationException
// for (int n : nums) {
// if (n % 2 == 0) nums.remove(Integer.valueOf(n));
// }
// ✅ use Iterator.remove()
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
if (it.next() % 2 == 0) it.remove();
}
System.out.println(nums); // [1, 3, 5]
// ✅ shorter: removeIf (Java 8+)
List<Integer> nums2 = new ArrayList<>(List.of(1, 2, 3, 4, 5));
nums2.removeIf(n -> n % 2 == 0);
System.out.println(nums2); // [1, 3, 5]
}
}The Collections Utility Class
Collections (note the "s") is a utility class providing static methods for sorting, searching, max/min, shuffle, reverse, immutable wrappers, etc. After Java 8 some of this has handier forms on List.sort / stream.
// CollectionsUtil.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CollectionsUtil {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9, 2, 6));
Collections.sort(nums);
System.out.println(nums); // ascending
Collections.sort(nums, Comparator.reverseOrder());
System.out.println(nums); // descending
System.out.println(Collections.max(nums));
System.out.println(Collections.min(nums));
Collections.shuffle(nums);
Collections.reverse(nums);
System.out.println(nums);
List<String> readonly = Collections.unmodifiableList(new ArrayList<>(List.of("a", "b")));
// readonly.add("c"); // UnsupportedOperationException
}
}