Store and look up data with List, Set, Map, and the Deque, and use generics so collections stay type-safe.
Why: the < > says what a collection holds. List<String> is "a list of Strings" — the compiler then stops you adding the wrong type and saves you from casting on the way out. This is what "type-safe" means.
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>(); // <> on the right is inferred
names.add("Ada");
names.add("Alan");
// names.add(42); // compile error — only Strings allowed
String first = names.get(0); // no cast needed, it's already a StringWhy: an array has a fixed size set at creation. An ArrayList grows and shrinks as you add and remove items, which is what you usually want. Reach for ArrayList unless you have a specific reason to use a raw array.
import java.util.ArrayList;
import java.util.List;
List<Integer> scores = new ArrayList<>();
scores.add(90);
scores.add(85);
scores.remove(0); // remove by index -> {85}
System.out.println(scores.size()); // 1 (a method, unlike array.length)
System.out.println(scores.contains(85)); // trueWhy: the for-each loop reads every element cleanly. To build or filter, you add to a new list as you go. (The Streams lesson shows an even shorter way for transforming data.)
import java.util.List;
List<String> names = List.of("Ada", "Alan", "Grace"); // quick read-only list
for (String name : names) {
System.out.println(name.toUpperCase());
}Why: a Set holds each value at most once — adding a duplicate is silently ignored. Use it to remove duplicates or test membership. A HashSet is fast but unordered.
import java.util.HashSet;
import java.util.Set;
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("java"); // ignored — already present
tags.add("backend");
System.out.println(tags.size()); // 2
System.out.println(tags.contains("java")); // trueWhy: a Map links keys to values, like a dictionary — look up a value by its key instead of by position. put() adds or updates, get() retrieves, and getOrDefault() avoids a null when the key is missing.
import java.util.HashMap;
import java.util.Map;
Map<String, Integer> ages = new HashMap<>();
ages.put("Ada", 36);
ages.put("Alan", 41);
System.out.println(ages.get("Ada")); // 36
System.out.println(ages.getOrDefault("Bob", 0)); // 0 — key not present
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " is " + entry.getValue());
}Why: a Deque (double-ended queue) adds and removes from both ends, so it serves as both a stack (last-in first-out) and a queue (first-in first-out). It is the recommended replacement for the older Stack class.
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> stack = new ArrayDeque<>();
stack.push("a");
stack.push("b");
System.out.println(stack.pop()); // "b" — last in, first out
Deque<String> queue = new ArrayDeque<>();
queue.offer("first");
queue.offer("second");
System.out.println(queue.poll()); // "first" — first in, first outWhy: removing from a list inside a normal for-each loop throws an error. An Iterator lets you walk the collection and safely remove the current element with its own remove() method.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
if (it.next() % 2 == 0) it.remove(); // drop even numbers
}
System.out.println(nums); // [1, 3]