Collections Framework
The Java Collections Framework (JCF) is a unified set of interfaces and implementations for storing and manipulating groups of objects. Instead of hand-rolling resizable arrays or linked structures, you program against stable interfaces — List, Set, Map, Queue — and swap implementations freely. The framework lives in java.util and has anchored every non-trivial Java application since Java 2.
The Hierarchy
Two root abstractions sit at the top. Iterable (and its child Collection) model a group of elements; Map models key-to-value associations and deliberately stands apart — a map is not a Collection.
Iterable
|
Collection
/ | \
List Set Queue
| | \
ArrayList HashSet Deque
LinkedList TreeSet ArrayDeque
LinkedHashSet PriorityQueue
Map (separate root — not a Collection)
|
HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
A
Mapis not aCollection. It exposes its contents as collections viakeySet(),values(), andentrySet(), but you cannot pass aMapwhere aCollectionis expected.
Choosing a Structure
Each top-level interface answers a different question: ordering, uniqueness, and access pattern.
| Interface | Duplicates | Ordering | Key access | Typical use |
|---|---|---|---|---|
List | Allowed | Insertion (indexed) | By index | Ordered sequences, positional access |
Set | Forbidden | Depends on impl | By element | Uniqueness, membership tests |
Map | Unique keys | Depends on impl | By key | Lookups, associations, caches |
Queue | Allowed | FIFO / priority | By head/tail | Pipelines, work queues, BFS |
A quick decision guide:
- Need positional order and indexed access? →
List(defaultArrayList). - Need uniqueness or fast membership checks? →
Set(defaultHashSet). - Need to look something up by a key? →
Map(defaultHashMap). - Need FIFO/LIFO/priority processing? →
Queue/Deque(defaultArrayDeque).
A Concrete Example
List<String> tasks = new ArrayList<>(List.of("build", "test", "build"));
Set<String> unique = new HashSet<>(tasks);
Map<String, Integer> counts = new HashMap<>();
for (String t : tasks) counts.merge(t, 1, Integer::sum);
System.out.println("tasks : " + tasks);
System.out.println("unique: " + unique);
System.out.println("counts: " + counts);
Output:
tasks : [build, test, build]
unique: [test, build]
counts: {test=1, build=2}
Note how the List preserved the duplicate build, the HashSet collapsed it, and the Map tallied occurrences.
Fail-Fast Iteration
Most java.util collections return fail-fast iterators: structurally modifying a collection while iterating (outside the iterator’s own remove) throws ConcurrentModificationException. This is a best-effort bug detector, not a concurrency guarantee.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3));
for (Integer n : nums) {
if (n == 2) nums.remove(n); // ConcurrentModificationException
}
Use Iterator.remove(), removeIf(...), or a concurrent collection instead.
Best Practices
- Declare variables by interface type (
List<T> x = ...), not implementation, so you can swap backends. - Pre-size collections (
new ArrayList<>(expectedSize)) when the count is known to avoid resize churn. - Prefer immutable factories (
List.of,Set.of,Map.of) for fixed data — they are compact and defensive. - Never store mutable objects in a
HashSet/asHashMapkeys if the fields used byhashCode()can change. - Reach for
Collections.emptyList()/Map.of()overnullto model “no elements.”
Interview Questions
Q: Why is Map not part of the Collection interface?
A: A Collection models a group of single elements with a uniform add(E) contract. A Map stores pairs and is keyed, so a single add method does not fit. It instead exposes collection views (keySet, values, entrySet).
Q: What is the difference between fail-fast and fail-safe iterators?
A: Fail-fast iterators (ArrayList, HashMap) track a modCount and throw ConcurrentModificationException on concurrent structural modification. Fail-safe iterators (CopyOnWriteArrayList, ConcurrentHashMap) iterate over a snapshot or tolerate concurrent change without throwing, at the cost of possibly stale data.
Q: How does HashMap work internally?
A: It maintains an array of buckets. A key’s hashCode() is spread and masked to a bucket index; entries are stored in that bucket. Collisions chain in a linked list, which converts to a balanced tree (red-black) once a bucket exceeds 8 entries (and table size ≥ 64), giving O(log n) worst-case lookups instead of O(n).
Q: When would you choose a LinkedList over an ArrayList?
A: Rarely. LinkedList only wins when you frequently insert/remove at the ends or via an iterator’s cursor. For nearly all index-based or random-access workloads, ArrayList is faster and more cache-friendly.