The Collections Framework
Java's built-in family of resizable data structures — List, Set, and Map — and when to reach for each.
2 min read
Arrays are fixed-size and fairly low-level. For almost everything else, Java code reaches for the Collections Framework — a set of interfaces and implementations for storing and manipulating groups of objects that can grow, shrink, and be queried far more flexibly.
List: ordered, allows duplicates
List is the closest thing to a resizable array — ordered, indexable, and duplicates are allowed. ArrayList is the implementation you'll reach for by default.
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>();
names.add("Ada");
names.add("Grace");
names.add("Ada"); // duplicates are fine
System.out.println(names.get(0)); // Ada
System.out.println(names.size()); // 3
names.remove("Grace");Note the pattern: declare the variable using the interface type (List<String>), but instantiate a concrete implementation (new ArrayList<>()). This is standard practice — it means your code depends on the general contract, not a specific implementation, so swapping ArrayList for LinkedList later requires changing only one line.
Set: no duplicates
Set guarantees every element is unique — adding a duplicate is silently a no-op. HashSet is the common default when order doesn't matter; LinkedHashSet preserves insertion order if you need that too.
import java.util.HashSet;
import java.util.Set;
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Ada");
uniqueNames.add("Ada"); // ignored -- already present
System.out.println(uniqueNames.size()); // 1Map: key-value pairs
Map associates unique keys with values — it's not technically part of the Collection interface hierarchy, but it's grouped with the framework because it's used just as constantly. HashMap is the standard default.
import java.util.HashMap;
import java.util.Map;
Map<String, Integer> ages = new HashMap<>();
ages.put("Ada", 36);
ages.put("Grace", 85);
System.out.println(ages.get("Ada")); // 36
System.out.println(ages.containsKey("Bob")); // false
ages.put("Ada", 37); // overwrites the existing value for that keyIterating collections
for (String name : names) {
System.out.println(name);
}
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " is " + entry.getValue());
}Choosing the right one
- Need order and duplicates, indexed access? Use a List.
- Need to guarantee uniqueness, don't care about order? Use a Set.
- Need to look things up by a key? Use a Map.
These three interfaces, backed by ArrayList, HashSet, and HashMap respectively, cover the overwhelming majority of everyday data-handling needs in Java — reaching for a raw array should be the exception once you need to grow, shrink, or search a collection.
Convenience factory methods
Since Java 9, List.of(...), Set.of(...), and Map.of(...) create small, immutable collections in a single line, without the multi-line add/put dance:
List<String> fixedNames = List.of("Ada", "Grace", "Alan");
Map<String, Integer> fixedAges = Map.of("Ada", 36, "Grace", 85);These are convenient for constants and test data, but calling .add() or .put() on the result throws an UnsupportedOperationException — they're genuinely read-only, not just "please don't modify this" by convention. Reach for new ArrayList<>() or new HashMap<>() instead whenever the collection needs to grow or change after creation.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.