A unified set of interfaces (List, Set, Queue, Map) and ready-made implementations — pick the interface for the behaviour you need, then the class for the performance you need.
The hierarchy
Iterable<T> → Collection<E> → List / Set / Queue
Iterable— anything usable in a for-each loop (iterator(),forEach).Collection—add,remove,contains,size,isEmpty,stream(),removeIf.List— ordered, index-based, duplicates allowed:ArrayList,LinkedList, (Vector,Stack— legacy).Set— no duplicates:HashSet,LinkedHashSet,TreeSet(SortedSet→NavigableSet).Queue/Deque— ends-oriented:ArrayDeque,LinkedList,PriorityQueue.Map<K, V>— key → value pairs. Not aCollection:HashMap,LinkedHashMap,TreeMap, (Hashtable— legacy).
Choosing a collection
| Need | Use | Why |
|---|---|---|
| Ordered list, fast random access | ArrayList | Array-backed, O(1) get(i), cache-friendly |
| Stack or queue | ArrayDeque | O(1) at both ends, faster than Stack / LinkedList |
| Unique elements, no order | HashSet | O(1) average add/contains |
| Unique, insertion order | LinkedHashSet | Hash lookup + linked list of entries |
| Unique, sorted / range queries | TreeSet | Red-black tree, O(log n), floor/ceiling |
| Key → value lookup | HashMap | O(1) average |
| Keys in insertion / access order, LRU | LinkedHashMap | Predictable iteration, removeEldestEntry |
| Sorted keys, nearest-key queries | TreeMap | O(log n), floorKey, subMap |
| Always get the min / max next | PriorityQueue | Binary heap: O(log n) offer/poll, O(1) peek |
| Thread-safe map | ConcurrentHashMap | Fine-grained locking, no nulls |
Program to the interface
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(); // declare the interface type
names.add("Asha");
names.add("Ravi");
names.add("Asha"); // duplicates are fine in a List
System.out.println(names + " size=" + names.size());
Set<String> unique = new TreeSet<>(names); // copy constructor: every collection has one
System.out.println(unique);
Map<String, Integer> ages = new HashMap<>();
ages.put("Asha", 30);
ages.put("Asha", 31); // same key: value replaced
System.out.println(ages);
}
}[Asha, Ravi, Asha] size=3
[Asha, Ravi]
{Asha=31}Declaring List<String> names (not ArrayList<String>) lets you swap the implementation later without touching the callers.
Immutable factories: List.of, Set.of, Map.of (Java 9+)
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> nums = List.of(3, 1, 2);
Map<String, Integer> m = Map.of("a", 1);
System.out.println(nums + " " + m);
try {
nums.add(4);
} catch (UnsupportedOperationException e) {
System.out.println("add -> UnsupportedOperationException");
}
try {
List.of("x", null);
} catch (NullPointerException e) {
System.out.println("null element -> NullPointerException");
}
try {
Set.of(1, 1);
} catch (IllegalArgumentException e) {
System.out.println("duplicate -> " + e.getMessage());
}
List<Integer> copy = List.copyOf(new ArrayList<>(nums)); // immutable snapshot
System.out.println(copy);
}
}[3, 1, 2] {a=1}
add -> UnsupportedOperationException
null element -> NullPointerException
duplicate -> duplicate element: 1
[3, 1, 2]List.of(...) | Arrays.asList(...) | Collections.unmodifiableList(l) | |
|---|---|---|---|
| Add / remove | Throws | Throws (fixed size) | Throws |
set(i, x) | Throws | Allowed (writes through to the array) | Throws |
null elements | Rejected | Allowed | Allowed |
| Sees later changes to the source? | No source | Yes (backed by the array) | Yes — it is only a read-only view |
Legacy classes
| Legacy | Problem | Modern replacement |
|---|---|---|
Vector | Every method synchronized — slow even single-threaded | ArrayList (or CopyOnWriteArrayList) |
Stack | Extends Vector; exposes get(i), insertElementAt | ArrayDeque (push/pop/peek) |
Hashtable | Global lock, no null keys/values | HashMap / ConcurrentHashMap |
Enumeration | No remove, clunky names | Iterator |
Why doesn't Map extend Collection?
Collection vs Collections?
Why can't a collection hold int?