ArrayList and HashMap
Two collections that grow as you need them: a numbered list, and a set of keyed values where a word is the address.
When the size is not known in advance
An array is fixed at the moment it is built. Very often you do not know how many items there will be, so Java ships collections that grow as items arrive. The two you will use constantly are ArrayList, which is a resizable list, and HashMap, which stores a value under a key.
An ArrayList keeps its items in order and numbers them, exactly like an array. add appends to the end, get(0) reads the first, size() counts them, contains answers whether something is present, and remove takes an item out, sliding the rest along.
Angle brackets say what goes inside
The angle brackets hold the type of the contents: List<String> is a list of text, Map<String, Integer> is a map from text to whole numbers. Java checks them, so a mistake is caught while the file compiles instead of when it runs.
Note the type on the left: the code declares List<String> and builds an ArrayList<>. The interface is what the rest of the code should depend on, and the concrete class is the one detail that is easy to swap later.
Keys, values, and no order at all
A HashMap stores pairs. stock.put("bread", 4) records 4 under the key bread; stock.get("bread") reads it back; getOrDefault supplies a fallback when the key is unknown, which is the tidy way to start a count at zero.
A hash map promises nothing about the order of its keys. Two runs of the same program may print them differently, and so may two versions of Java. When a report has to be readable, collect the keys, sort them, and walk that sorted list — which is what the example does.
Boxing, and the type inside the brackets
A collection cannot hold a primitive directly. List<Integer> stores Integer objects, and Java boxes an int into one on the way in and unboxes it on the way out. That is why the type is Integer and not int, and why a null in the list can fail when it is unboxed.
The compiler checks the type inside the brackets, so list.add("text") on a List<Integer> fails before the program runs. The check is erased at run time, but the compile error is the part that matters while you write.
Keys, equality and removal
A HashMap finds a key by its hashCode and then confirms it with equals. Two strings with the same characters work as the same key even when they are different objects, which is exactly what you want.
List.remove(0) removes by position and returns the removed item; List.remove("bread") removes the first matching value. With a List<Integer>, remove(1) is the index form and remove(Integer.valueOf(1)) is the value form — a distinction worth knowing before it bites.
Removing while you walk
The enhanced for walks the list through an iterator. Changing the list underneath that walk — names.remove(...) inside the loop — makes the iterator fail with ConcurrentModificationException, because the list has changed in a way the iterator did not expect. The check is deliberate: it turns a silent skip into a loud error.
names.removeIf(name -> name.startsWith("A")) is the modern answer. It removes every match in one call and does the walking itself, so there is no iterator to upset. The older answer is an explicit Iterator with it.remove(), which is safe because the iterator is the thing doing the removing. Either way, the rule is that only the walker may change the collection.
A map is not a list
A map is the wrong tool when order matters and the position is the question. List keeps the order you added things in and lets you ask for the third item; HashMap keeps neither promise. Reach for the map when you know the key and want the value, and for the list when you know the position or need to keep the sequence.
A list and a map, side by side
import java.util.*;
List<String> names = new ArrayList<>();
names.add("Ada");
names.add("Grace");
System.out.println(names.size()); // 2
System.out.println(names.get(0)); // Ada
System.out.println(names.contains("Ada")); // true
names.remove("Ada");
System.out.println(names); // [Grace]
Map<String, Integer> counts = new HashMap<>();
counts.put("ada", 1);
counts.put("ada", counts.get("ada") + 1);
System.out.println(counts.get("ada")); // 2
System.out.println(counts.getOrDefault("bob", 0)); // 0
System.out.println(counts.containsKey("bob")); // falseA shopping list and a stock count
Press Run and follow the five lines. Then add a fourth item to the map, and make the last loop print the total count instead of each pair.
Three collection mistakes: removing from a list inside an enhanced for (use an iterator or collect the items first), comparing boxed Integer values with == instead of equals, and expecting a HashMap to keep insertion order. The first throws a ConcurrentModificationException; the second can be true for small values and false for large ones.
Try these next
- Count how many times each word appears in a short array, then print the keys in alphabetical order.
- Add three names to a
List, remove the middle one by value, and print the list before and after. - Use
getOrDefaultto count a word that is not in the map yet, and explain the starting value.
Word counts, sorted for reading
Press Run and check the counts against the input array. Then add two more words to the array, and print the most frequent word without sorting the keys first.
Print a list directly and you see its contents in square brackets; print a map directly and you see its pairs in curly braces, in an order not worth remembering. Sort the keys first when the output has to be stable.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What can an ArrayList do that an array cannot?
