The Collections framework, mapped out
The arrays lesson ended with a list of things arrays are not for: anything that grows, anything looked up by key, anything you check membership in. The Collections framework is the answer to all three, and it is the part of the standard library you will use every working day.
This lesson is the map. The next five are the territory.
The three shapes
Almost everything reduces to three questions about what you are storing.
| Question | Answer | Java type |
|---|---|---|
| An ordered sequence, duplicates allowed | Position matters | List |
| A bag of unique things, order irrelevant | "Have I seen this?" | Set |
| Things found by a key | "What is the value for X?" | Map |
List<String> deliveryOrder = new ArrayList<>(); // Priya, then Arjun, then Kavita
Set<String> areasServed = new HashSet<>(); // Wagholi, Kharadi — each once
Map<String, Integer> tiffinsByName = new HashMap<>(); // "Priya" -> 26
Choosing wrongly is the most common design mistake in beginner Java. A List
used for membership checks is slow and full of duplicates; a Map used for an
ordered sequence loses the order. Getting this right is worth more than knowing
every method on ArrayList.
The hierarchy, only as much as you need
Iterable
|
Collection ───────────────┐
/ \ │
List Set (Map is NOT a Collection)
| | |
ArrayList HashSet HashMap
LinkedList LinkedHashSet LinkedHashMap
TreeSet TreeMap
Two things to take from that picture.
Map is not a Collection. It has no add, it is not Iterable, and you
cannot pass one where a Collection is expected. It holds pairs, which is a
different shape. You iterate its entrySet(), keySet() or values(), each of
which is a collection.
Iterable is what the enhanced for loop needs. Anything implementing it
works with for (T x : thing), including your own classes.
The implementations you will actually use
| Interface | Implementation | Ordering | When |
|---|---|---|---|
List |
ArrayList |
Insertion | The default. Fast access by index |
List |
LinkedList |
Insertion | Rarely. Queue or deque behaviour |
Set |
HashSet |
None | The default. Fast membership |
Set |
LinkedHashSet |
Insertion | When you need order and uniqueness |
Set |
TreeSet |
Sorted | When you need sorted order maintained |
Map |
HashMap |
None | The default. Fast lookup |
Map |
LinkedHashMap |
Insertion | Predictable iteration, caches |
Map |
TreeMap |
Sorted by key | Ranges, sorted reports |
Deque |
ArrayDeque |
Insertion | Stacks and queues |
Start with ArrayList, HashSet and HashMap. Move off them only for a
reason you can state. LinkedList in particular is almost always the wrong
answer despite its prominence in textbooks — the choosing lesson at the end of
this module measures it.
Declare the interface, create the implementation
List<String> names = new ArrayList<>();
Map<String, Integer> counts = new HashMap<>();
Not ArrayList<String> names = new ArrayList<>(). The variable's type is the
promise you make to the rest of the code; the implementation is a decision you
might change. Swapping HashMap for LinkedHashMap should be a one-word edit,
and it is only if nothing depends on the concrete type.
The same applies to parameters and return types. A method taking List<String>
can be called with any list; one taking ArrayList<String> cannot.
Creating them
Four ways, with different results — and the differences catch people.
// 1. Empty and growable
List<String> a = new ArrayList<>();
// 2. Immutable, from Java 9. Cannot add, remove or set. Rejects nulls.
List<String> b = List.of("Priya", "Arjun");
// 3. A fixed-size view of an array. set() works, add() does not.
List<String> c = Arrays.asList(array);
// 4. A growable copy of any of the above
List<String> d = new ArrayList<>(b);
List.of("Priya").add("Arjun");
UnsupportedOperationException
List.of, Set.of and Map.of are immutable. That is usually a feature —
use them for constants and for anything you hand out — but it is the commonest
surprise in modern Java, because the exception mentions nothing about
immutability.
Arrays.asList is stranger still: it is a view of the array, so set writes
through to the array and add throws.
String[] arr = {"Priya", "Arjun"};
List<String> view = Arrays.asList(arr);
view.set(0, "Kavita");
System.out.println(Arrays.toString(arr));
[Kavita, Arjun]
When you want something you can modify freely, wrap it: new ArrayList<>(...).
Methods every collection has
From Collection, so they work on both List and Set:
| Method | Does |
|---|---|
add(e) |
Adds. Returns false on a Set if already present |
remove(o) |
Removes one matching element |
contains(o) |
Membership, using equals |
size() |
Count |
isEmpty() |
Count is zero |
clear() |
Removes everything |
addAll(c) / removeAll(c) / retainAll(c) |
Bulk operations |
removeIf(predicate) |
Removes everything matching |
forEach(action) |
Applies an action to each |
stream() |
Opens module 6 |
iterator() |
The manual way to walk it |
Note contains uses equals, not ==. Which means it only works for your
own classes if you have written equals and hashCode correctly — the lesson
later in this module that exists because getting it wrong produces silent
duplicates.
Iterating
for (String name : names) { ... } // usual
names.forEach(name -> { ... }); // module 6 style
for (int i = 0; i < names.size(); i++) { ... } // only when you need the index
for (Map.Entry<String, Integer> e : counts.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
For a map, iterate entrySet() when you need both halves. Iterating
keySet() and calling get inside the loop does the lookup work twice.
And one rule that has its own section in the next lesson: do not modify a
collection while a for loop is walking it. The consequences are inconsistent
enough to be genuinely dangerous.
What the framework is not for
- Primitives without boxing.
List<Integer>stores objects. For millions of numbers that costs memory and speed; use an array orIntStream. - Thread safety.
ArrayListandHashMapare not safe for concurrent modification. Module 9 covers what to use instead — and the answer is notVectororHashtable, which are obsolete. - Sorted order for free. A
HashMaphas no order, and the order it happens to show is not insertion order and may change between runs and versions. Never rely on it.
Check your work
What are the three shapes and their questions? List — position matters,
duplicates allowed. Set — have I seen this before. Map — what is the value
for this key.
Why is Map not a Collection? It holds pairs rather than elements. It has
no add, is not Iterable, and exposes entrySet(), keySet() and values()
when you want a collection view.
Why declare List rather than ArrayList? The interface is the promise; the
implementation is a decision you may change. Depending on the concrete type
makes swapping it a wide edit.
What does List.of(...).add(...) do? Throws UnsupportedOperationException.
The factory methods produce immutable collections.
How does Arrays.asList differ from List.of? It is a fixed-size view of
the array — set writes through to the array, add throws. List.of is a
fully immutable copy that also rejects nulls.
Which three implementations are the defaults? ArrayList, HashSet,
HashMap. Move off them only for a reason you can state.
Why iterate entrySet() rather than keySet()? Iterating keys and calling
get inside the loop performs the lookup twice for every entry.
Practice 3, the right shape for each. Delivery order for a round is a List
— sequence matters and the same customer could appear twice on a split round.
Areas served is a Set — each once, membership is the question. Tiffins per
customer is a Map. Customers per area is a Map<String, List<String>>, and
the next lesson but one shows the one-line way to build it.
Practice
-
Create all four kinds of list —
new ArrayList<>(),List.of(...),Arrays.asList(array)andnew ArrayList<>(List.of(...)). Tryaddandseton each and note which throw. -
Prove
Arrays.asListis a view. Build one from an array,setan element, then print the original array. -
Pick the shape. For each, name the type and say why in one clause: the order tiffins are delivered on a round; the areas your service covers; how many tiffins each customer took this month; which customers live in each area; whether a pincode is serviceable; the last ten complaints in the order they arrived.
-
Swap the implementation. Write a method taking
List<String>, call it with anArrayListand then withList.of(...). Then change the parameter toArrayList<String>and see which call stops compiling. -
Iterate a map both ways. Once over
keySet()withgetinside, once overentrySet(). Then put a print statement insideget— you cannot, so instead reason about how many lookups each version performs for a map of 1,000 entries. -
Harder — a delivery round. Build a
Map<String, List<String>>of area to customer names from a flat list of"Priya,Wagholi"lines. Print each area with its customers, areas in alphabetical order and customers in the order they appeared. Choosing the right map type for each of those two requirements is the exercise.
Next: List and ArrayList in detail, and the removal that behaves three
different ways.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship