The Collections Framework in one picture
An array has a fixed length chosen when you create it. Real programs rarely know that number in advance — a shopping cart grows, a set of logged-in users shrinks — so Java ships the Collections Framework in java.util: a small set of interfaces describing what a container can do, and several implementations of each, which differ in how they store data and therefore in what is fast.
| Interface | What it promises | Main implementations |
|---|---|---|
List<E> | Ordered by position, duplicates allowed, get(i) | ArrayList, LinkedList |
Set<E> | No duplicates | HashSet, LinkedHashSet, TreeSet |
Map<K,V> | Unique keys, each mapped to one value | HashMap, LinkedHashMap, TreeMap |
Queue<E> / Deque<E> | Take from the head (and tail for Deque) | ArrayDeque, PriorityQueue, LinkedList |
The habit that separates juniors from everyone else: declare the variable with the interface type, create it with the implementation — List<String> names = new ArrayList<>();. The rest of the code only relies on "it is a List", so switching to a different implementation later is a one-word change. The empty <> (the diamond) tells the compiler to infer the type argument from the left-hand side.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Ada");
names.add("Linus");
names.add("Ada"); // duplicates are fine in a List
System.out.println(names + " size=" + names.size());
Set<String> unique = new TreeSet<>(names); // copy into a Set
System.out.println(unique);
Map<String, Integer> ages = new HashMap<>();
ages.put("Ada", 36);
System.out.println(ages.get("Ada") + " " + ages.get("Bob"));
}
}[Ada, Linus, Ada] size=3
[Ada, Linus]
36 nullAdd "Grace" to names before the copy and check that it lands in the right sorted spot in unique.
Immutable factory methods
List.of(...), Set.of(...) and Map.of(...) build small unmodifiable collections in one line. They are perfect for constants and test data, but any attempt to change them throws at runtime, and they reject null elements outright.
UnsupportedOperationException: adding to List.of(...)
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> colors = List.of("red", "green");
colors.add("blue");
System.out.println(colors);
}
}Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(Unknown Source)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(Unknown Source)
at Main.main(Main.java:6)List.of returns an unmodifiable list. It still has an add method — every List must — but that method is implemented to throw. The compiler cannot tell the difference, so this is a runtime failure, not a compile error.
When you need to change the list, copy it into a mutable implementation: new ArrayList<>(List.of(...)).
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> colors = new ArrayList<>(List.of("red", "green"));
colors.add("blue");
System.out.println(colors);
}
}unexpected type: List<int>
import java.util.*;
public class Main {
public static void main(String[] args) {
List<int> scores = new ArrayList<>();
}
}Main.java:5: error: unexpected type
List<int> scores = new ArrayList<>();
^
required: reference
found: int
1 errorGenerics only work with reference types (objects). int is a primitive, so it cannot be a type argument. Every primitive has a wrapper class — Integer, Long, Double, Boolean, Character — and Java converts between them automatically (autoboxing).
Use the wrapper type: List<Integer>. You can still write scores.add(90) and int s = scores.get(0).
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>();
scores.add(90);
int first = scores.get(0);
System.out.println(first);
}
}