Java Collections Framework Overview
A unified architecture of interfaces and implementations for storing and manipulating groups of objects — the foundation for List, Set, Queue, and Map.
Before Java 1.2, every container type (arrays, Vector, Hashtable) had its own inconsistent API. The Java Collections Framework unified this into a small set of interfaces (Collection, List, Set, Queue, Map) with multiple interchangeable implementations, consistent iteration, and generic type safety, so code can be written against an interface and swap implementations freely.
What Is the Collections Framework
The Collections Framework, primarily in java.util, is a set of interfaces defining what operations a group-of-objects data structure supports, plus concrete classes implementing those interfaces with different internal strategies (array-backed, linked-node-based, hash-table-based, tree-based). Writing code against the interface type (List, Set, Map) rather than a concrete class lets you change the underlying implementation later with minimal code changes.
Collection versus Map: A Key Distinction
Collection is the root interface for groups of individual elements — List, Set, and Queue all extend it. Map is a separate root interface entirely: it stores key-to-value associations, not standalone elements, and Map does NOT extend Collection. This is a deliberate design choice in the JLS-adjacent library design, not an oversight — a Map's fundamental operation (look up a value by key) doesn't fit the single-element contract (add, remove, iterate one element at a time) that Collection defines.
Map does provide bridge methods into Collection-based views — keySet() returns a Set of keys, values() returns a Collection of values, entrySet() returns a Set of Map.Entry pairs — but the Map object itself is never assignable to a Collection reference.
Iterable
└── Collection
├── List (ArrayList, LinkedList, Vector)
├── Set (HashSet, LinkedHashSet, TreeSet)
└── Queue (ArrayDeque, PriorityQueue, LinkedList)
└── Deque
Map (separate hierarchy — NOT a Collection)
├── HashMap
├── LinkedHashMap
└── TreeMapCore Interfaces
Iterable is the very top: anything Iterable can be used in a for-each loop. Collection extends Iterable and adds the general contract (add, remove, size, contains, iterator). List adds positional/indexed access and permits duplicates in a defined order. Set forbids duplicate elements. Queue models a collection designed for holding elements prior to processing (typically FIFO); Deque extends Queue with efficient operations at both ends.
Common Implementations
Each interface has multiple implementations trading off different performance characteristics and ordering guarantees:
List: ArrayList (resizable array), LinkedList (doubly linked list),
Vector (legacy, synchronized)
Set: HashSet (hash table, no order), LinkedHashSet (hash table +
insertion order), TreeSet (sorted, red-black tree)
Queue: ArrayDeque (resizable array deque), PriorityQueue (heap-based),
LinkedList (also implements Deque)
Map: HashMap, LinkedHashMap, TreeMap (each a Map, not a Collection)Iteration
Every Collection is Iterable, so it supports the for-each loop and can also produce an explicit Iterator via iterator(), which supports safe removal during iteration via Iterator.remove(). Modifying most collections directly while iterating with for-each (rather than through the iterator's own remove method) throws ConcurrentModificationException.
import java.util.*;
List<String> names = new ArrayList<>(List.of("Ann", "Bo", "Cy"));
for (String name : names) { // for-each, uses Iterable
System.out.println(name);
}
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("Bo")) {
it.remove(); // safe removal during iteration
}
}
System.out.println(names); // [Ann, Cy]Generics in Collections
Since Java 5, collection interfaces and classes are generic (List<E>, Map<K, V>), giving compile-time type checking instead of the pre-generics pattern of storing Object and casting on retrieval. Prefer the interface type on the left of a declaration (List<String> list = new ArrayList<>();) — this is idiomatic and keeps code flexible to swap implementations. Avoid raw types (List instead of List<String>) except when required by legacy APIs, since raw types lose compile-time type checking.
Mutability
Most standard implementations (ArrayList, HashMap, etc.) are mutable. The framework also provides unmodifiable views (Collections.unmodifiableList(...)) that throw UnsupportedOperationException on any mutating call, and, since Java 9, truly immutable factory-created collections (List.of(...), Set.of(...), Map.of(...)) that reject mutation the same way and also reject null elements.
List<String> mutable = new ArrayList<>();
mutable.add("a"); // fine
List<String> immutable = List.of("x", "y");
// immutable.add("z"); // throws UnsupportedOperationExceptionCommon Selection Criteria
Choosing an implementation is a matter of matching its guarantees to your needs: Do you need indexed access or insertion order (List)? Must elements be unique (Set)? Do you need sorted order (TreeSet/TreeMap) or predictable iteration order matching insertion (LinkedHashSet/LinkedHashMap)? Is this single-threaded code, or does it need thread-safety (in which case explicit synchronization, Collections.synchronizedList, or a java.util.concurrent collection is needed — none of the common implementations listed here are thread-safe by default)?
Practical Usage
import java.util.*;
public class InventoryDemo {
public static void main(String[] args) {
List<String> restockOrder = new ArrayList<>(); // ordered, duplicates OK
Set<String> inStock = new HashSet<>(); // uniqueness matters
Map<String, Integer> quantities = new HashMap<>(); // key-value, NOT a Collection
restockOrder.add("widget");
restockOrder.add("widget"); // duplicate allowed in a List
inStock.add("widget");
inStock.add("widget"); // second add is a no-op — Set enforces uniqueness
quantities.put("widget", 42);
System.out.println(restockOrder.size()); // 2
System.out.println(inStock.size()); // 1
System.out.println(quantities.get("widget")); // 42
}
}Edge Cases
Because Map is not a Collection, you cannot pass a Map directly to a method expecting Collection<T>, and Map has no iterator() of its own — you iterate its keySet(), values(), or entrySet() view instead.
Collections returned by List.of, Set.of, and Map.of reject null elements/keys/values, throwing NullPointerException, unlike some traditional mutable implementations (ArrayList permits null elements; HashMap permits one null key).
Generics use type erasure at runtime — at the bytecode level a List<String> and a List<Integer> share the same runtime class, java.util.ArrayList; this is why you cannot do new T[10] or overload solely on generic type parameter, though reflective metadata in the class file can still record the declared generic signatures in many cases.
Mental Model
Picture two separate filing systems in the same office. One system (Collection: List, Set, Queue) files individual documents into folders — some folders keep order and allow duplicates, some enforce uniqueness, some are optimized for processing order. The other system (Map) is a completely different mechanism: a labeled lookup index where you retrieve a document by its label (key), not by iterating documents directly. They share the same building (java.util) and similar naming conventions, but they are not the same kind of thing.
Quick Reference
Collection (Iterable) Map (separate hierarchy)
├── List — ordered, dup OK ├── HashMap — no order
├── Set — no dup ├── LinkedHashMap — insertion order
│ └── SortedSet/TreeSet ├── TreeMap — sorted by key
└── Queue/Deque — process order └── (keySet/values/entrySet bridge to Collection)
Prefer: List<String> list = new ArrayList<>(); (interface type, generic)
Immutable: List.of(...), Set.of(...), Map.of(...) — reject null, reject mutation
None of the common implementations are thread-safe by default.Key Takeaways
The Collections Framework provides a small, consistent set of interfaces (List, Set, Queue/Deque under Collection) with many interchangeable implementations.
Map is a separate root interface and does not extend Collection — it stores key-value associations, and its Collection-based views (keySet, values, entrySet) are how it bridges to the Collection world.
Every Collection is Iterable and supports for-each; safe removal during iteration requires the Iterator's own remove() method.
Generics give compile-time type safety for collections; prefer declaring the interface type and avoid raw types.
None of the common implementations (ArrayList, HashMap, HashSet, etc.) are thread-safe by default; that requires explicit synchronization or a concurrent collection.
void printAll(Collection<String> items) {... } Map<String, Integer> map = new HashMap<>(); printAll(map); // does NOT compile void printAll(Collection<String> items) {... } Map<String, Integer> map = new HashMap<>(); printAll(map.keySet()); // Set<String> is-a Collection<String>
Map is not a subtype of Collection; passing a Map where a Collection is expected does not compile. Use one of its views (keySet, values, entrySet) instead.
List rawList = new ArrayList(); // raw type, no compile-time checking rawList.add("text"); rawList.add(42); // compiles, but mixes types silently
List<String> list = new ArrayList<>(); list.add("text"); // list.add(42); // now a compile error, caught early
Using a raw type discards generic type checking, allowing type-mixing bugs to surface only at runtime (typically as a ClassCastException far from the actual mistake).
for (String s : list) { if (s.isEmpty()) { list.remove(s); // throws ConcurrentModificationException } }
Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().isEmpty()) { it.remove(); // safe } }
Structurally modifying most collections directly during a for-each loop throws ConcurrentModificationException; removal during iteration must go through the Iterator's own remove() method.
Why does Map not extend Collection, even though both are part of the Collections Framework?
Collection models a group of individual elements (add/remove/iterate one at a time), while Map models key-to-value associations — a fundamentally different contract — so the framework deliberately keeps Map as a separate root interface.
How can you get a Collection-compatible view of a Map's contents?
Through its bridge methods: keySet() returns a Set of keys, values() returns a Collection of values, and entrySet() returns a Set of Map.Entry pairs.
What does it mean that every Collection is Iterable, in terms of language features you get for free?
It means every Collection can be used directly in a for-each loop and can produce an explicit Iterator for manual traversal and safe removal.
Why is List<String> list = new ArrayList<>(); generally preferred over ArrayList<String> list = new ArrayList<>(); for a variable declaration?
Declaring the interface type keeps the code flexible to swap in a different List implementation later with minimal changes, and documents that only the List contract, not ArrayList-specific behavior, is being relied upon.
Are the common collection implementations like ArrayList and HashMap thread-safe by default?
No — none of them are thread-safe by default; concurrent access requires explicit synchronization, a synchronized wrapper, or a java.util.concurrent collection.
Interview: What does 'type erasure' mean for generics in the Collections Framework, and what's a common misconception about it?
At runtime, a List<String> and a List<Integer> share the same erased runtime class (java.util.ArrayList) — generic type parameters are not fully retained as runtime type tags for ordinary operations. The common misconception is that generic information vanishes entirely; in practice, class-file signatures and reflective metadata can still preserve declared generic type information in many scenarios.
Write code that builds a HashMap<String,Integer> of fruit-to-count, then prints its keys, values, and entries using the Map's Collection-based bridge views.
Map<String, Integer> counts = new HashMap<>(); counts.put("apple", 3); counts.put("banana", 5); Set<String> keys = counts.keySet(); Collection<Integer> values = counts.values(); Set<Map.Entry<String, Integer>> entries = counts.entrySet(); System.out.println(keys); System.out.println(values); for (Map.Entry<String, Integer> e: entries) { System.out.println(e.getKey() + " -> " + e.getValue()); } keySet() and entrySet() return Sets (no duplicate keys), values() returns a plain Collection (values can repeat); none of these change the fact that counts itself is a Map, not a Collection.
Does this line compile? Why or why not?
No — HashMap does not implement Collection, since Map is a separate interface hierarchy from Collection, so a HashMap reference cannot be assigned to a Collection variable.
Write a method that takes a List<Integer> and removes every even number, safely, without triggering ConcurrentModificationException.
static void removeEvens(List<Integer> nums) { Iterator<Integer> it = nums.iterator(); while (it.hasNext()) { if (it.next() % 2 == 0) { it.remove(); } } }
Using the Iterator's own remove() method (rather than list.remove() inside a for-each) keeps the iteration's internal state consistent, avoiding ConcurrentModificationException.
Collection<String> c = new HashMap<String, Integer>();