Java Map, HashMap, TreeMap, and LinkedHashMap
Look up a stock quantity, update a study-time record, and count words by their normalized spelling. Learn how keys relate to values, why missing mappings need a policy, and how to keep reports in a deliberate order.
Before you start: review set equality and ordering, boxing and nulls, and collection copies. Each runner contains a complete Java 21 Main.java program with no input required. Execution requires the existing isolated sandbox; there is no website-host fallback.
What you will be able to do
- Read Map<K, V> as a relationship between key and value types.
- Explain key replacement and distinguish missing keys from null values.
- Select unspecified, insertion, or sorted-key encounter order.
- Traverse key/value pairs with entrySet without unnecessary repeated lookup.
- Distinguish backed views from independent shallow copies.
- Count normalized word tokens while preserving first-seen key order.
1. A map connects a lookup key to a value
Imagine a tiny stock register: the product name tells you how many items are available. Keeping one list of names and a separate list of quantities is fragile because their positions must stay synchronized. A Map stores each key/value relationship together. Map<String, Integer> means String keys lead to Integer values; the types answer different questions.
A map has one current value per key. The first notebook put stores 4; the second replaces it with 7 and returns the previous 4. It does not add a second notebook entry. The pen can also have value 7 because values need not be unique. size counts mappings, not distinct values or the total number of physical items.
get reads by key rather than list position, and remove(key) removes the mapping and returns its former value, if present. A Map is part of the Collections Framework, but it is not a Collection subtype and cannot be traversed directly with enhanced for. Use its collection views, which we explore later in the lesson.
A repeated key replaces its value
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First put: null
Previous value: 4
Notebook stock: 7
Entries: 2
Removed pen stock: 7
2. Decide what missing data means before doing arithmetic
In a HashMap, get can return null because a key is absent or because that key is explicitly mapped to null. Our score example uses null for a known student without a recorded result. containsKey distinguishes that state from an unknown student. The two null printouts look identical, but the corresponding membership answers do not.
getOrDefault supplies a fallback only when there is no mapping. It returns the stored null for Mira, not zero, and it does not insert Noah into the map. Assigning that null to an int would still unbox and throw. Use Integer when inspecting nullable results and apply a documented missing-value policy before primitive arithmetic.
For counters, a simpler contract is often best: never store null values. Under that rule, getOrDefault(word, 0) safely represents a missing count. This is the rule used by our word-frequency helper, not a general promise made by all Maps. HashMap permits null keys and values; natural-order TreeMap rejects null keys but permits null values. Map.of rejects both nulls and duplicate keys and is unmodifiable.
Absent and present-with-null are different states
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Unknown get: null
Mira get: null
Has Noah: false
Has Mira: true
Unknown default: 0
Mira default: null
Default inserted Noah: false
Trace absence versus a stored null
- Noah has no mapping: containsKey is false and the fallback is zero.
- Mira has a mapping whose value is null: containsKey is true and the fallback is not substituted.
- No lookup inserts a new mapping in this example.
Change Mira to an actual zero score and compare the result. Recorded zero, missing result, and unknown student are different application states.
3. LinkedHashMap can preserve first-seen key order
A study-time report may need to keep topics in the sequence the student first selected them. The default LinkedHashMap constructor provides insertion-order traversal. Updating sql from 20 to 25 changes its value without moving the key. Removing that mapping and inserting it later puts it at the end of the sequence.
Do not generalize this behavior to every possible LinkedHashMap configuration. An access-order constructor can reorder entries when they are accessed; explicit sequencing methods can also reposition them in Java 21. Our examples deliberately use the default constructor and ordinary put, so their first-seen ordering promise is easy to understand.
HashMap does not promise a display order. A copied LinkedHashMap preserves the encounter sequence of its source at copy time; it cannot reconstruct historical insertions from an unordered source. In the lab we build a new LinkedHashMap directly while reading a List, so the first encounter of each normalized key defines its presentation position.
Update a value without moving its key
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Updated: {sql=25, java=30, css=10}
Reinserted: {java=30, css=10, sql=40}
4. TreeMap orders keys, not values
Use a TreeMap when key order is part of the requirement, such as a glossary or a word-frequency report shown by word. In the example, css, java, and sql determine traversal order. Their minute values are 10, 30, and 20, so the output is visibly not sorted numerically by value.
For String keys without a supplied Comparator, natural order compares UTF-16 code units. It is not automatically human dictionary order for all languages. A different comparator changes key comparison and also which keys are treated as the same: if comparison returns zero, a later put can replace a mapping even when the two keys are not equal by equals.
Keep the ordering consistent with equals to honor the general Map contract. A case-insensitive tree is not a harmless display change if case-sensitive identifiers must remain distinct. For our counting report, normalization defines the stored keys first, and a separate natural-order TreeMap copy controls sorted presentation without changing the first-seen map.
TreeMap sorts keys, not numeric values
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
{css=10, java=30, sql=20}
Keys: [css, java, sql]
5. Traverse entries when you need both sides of the relationship
entrySet provides a Set of Map.Entry objects. Each entry gives you its key and corresponding value through getKey and getValue. Read the loop declaration from the inside out: each item is a String-to-Integer pair belonging to this map. This is often clearer than traversing keys and doing a second lookup for every value.
The example prints each topic and adds its known non-null minutes to a long total. That non-null condition is part of this sample’s data, not guaranteed by Map<String, Integer>. If the data can be incomplete, validate or handle missing values before the addition unboxes them. keySet is useful when you need only keys; values is useful when you need only values and may include duplicates.
Keep this traversal read-only. Adding or removing mappings through an ordinary map while its iterator is in use is unsafe and can trigger best-effort fail-fast diagnostics. Do not depend on ConcurrentModificationException to enforce business rules or thread safety. Iterator-aware mutation receives its own lesson; building a separate result is a clear alternative here.
Read a key and its value together
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
sql: 20
java: 30
Total: 50
6. A map view is not an independent snapshot
keySet, values, and entrySet are backed views of a map. In the example, adding css to the map also changes what liveKeys shows. No second keySet call is required. Removing java through that supported key view removes the corresponding mapping from the map as well. A view is a window into the container, not a detached copy.
new LinkedHashSet<>(liveKeys) copies the current key references into a new set, so the later css insertion is not visible there. Similarly, new LinkedHashMap<>(original) creates an independent map container initialized with the current mappings. These are shallow copies: mutable key or value objects are not recursively cloned.
Do not assume every collection operation is available on a view. For example, adding a new key through keySet is unsupported because the view cannot invent its associated value. Unmodifiable maps restrict mutation through their views too. A method returning a map or view should clearly state whether callers may change it and whether future owner edits remain visible.
A key view is live; a copied set is separate
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Live: [sql, java, css]
Copy: [sql, java]
Map after view removal: {sql=20, css=10}
7. A counter is a read–update–store workflow
A frequency table associates each word with the number of occurrences seen so far. Start a missing key at zero, add one for the current occurrence, then put the updated count back under that same key. Replacing a value is useful here: you want one key per word and an increasing count, not a list of repeated mappings.
Trace java, sql, java by hand. The map starts empty; java becomes 1, sql becomes 1, and the second java reads 1 and writes 2. The first-seen key position remains unchanged. The code uses Integer values through boxing and unboxing, while all values inserted by this counter are present non-null numbers.
This read–update–store sequence is single-threaded, not an atomic concurrent counter. Two threads can otherwise read the same old value and lose an update. Advanced update APIs and concurrency are outside this course; changing one line to a convenience method does not automatically make an ordinary map thread-safe. The lab keeps ownership local and input stable.
Read the old count, add one, store the new count
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First-seen: {java=2, sql=1}
Sorted copy: {java=2, sql=1}
Trace the repeated java token
On its first encounter, getOrDefault returns zero and put stores one. On its second encounter, the old count is one and put stores two. The key is still a single entry at its first-seen position. Writing put(word, 1) every time would erase the accumulated count.
8. Stable key equality matters more than a convenient printout
HashMap uses hashing together with key equality. The Strings FB and Ea share a hash code but are not equal, so their two mappings remain distinct. A hash is not a unique identity. For custom key classes, implement equals and hashCode consistently using stable data, as we did for set elements in the previous lesson.
Do not modify a key’s equality or hashing state while it is stored in a hash-based map; likewise do not change ordering fields inside a TreeMap key. The container does not automatically reindex arbitrary object mutations. Immutable String keys are convenient in our examples. Mutable values are allowed, but sharing their references across shallow copies still needs attention.
HashMap basic lookup and put are typically constant-time with well-distributed hashes, LinkedHashMap adds ordering bookkeeping, and TreeMap key operations have logarithmic costs. These are implementation tradeoffs, not a reason to compromise required semantics. Choose membership-only, first-seen, or sorted-key presentation first, and benchmark a representative workload if actual performance becomes important.
Colliding hashes can still identify distinct keys
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Same hash: true
Entries: 2
FB value: 10
Ea value: 20
Repair exercise: the first word has no count yet
The first occurrence should create java=1, but this counter crashes before inserting it. Find the implicit unboxing and repair the missing-key read. The counter’s contract is that stored counts are never null.
Main.java · Locate implicit unboxing
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Current result:
Missing count was unboxed.
Reveal the explained repair
get returns null for an absent java key; addition tries to unbox it. getOrDefault supplies zero for this absent mapping, so the next value is one. It would not repair an explicit null value already stored under java. Keeping this counter’s non-null-value invariant is therefore part of the solution, not an optional assumption.
Main.java · Start an absent count at zero
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
{java=1}
Independent lab: a word-frequency report with two display orders
Build a frequency report from an already-tokenized List of words. Normalize surrounding whitespace and case, retain first-seen key order, and also display a sorted-key copy. This helper counts tokens; it does not split prose or strip punctuation.
- Implement countWords(List<String> tokens), returning a new editable Map<String, Integer>.
- Reject a null list and any null, empty, or strip-whitespace-only token with IllegalArgumentException.
- Normalize each token with strip().toLowerCase(Locale.ROOT); punctuation stays part of a key.
- Accumulate positive non-null counts in a default LinkedHashMap, keeping first-seen normalized key order.
- Leave the input untouched, including when a later token is invalid; empty input returns an empty map.
- Print first-seen counts, a separate TreeMap copy sorted by key, and the number of unique words.
The starter compiles but prints nothing. Implement the helper and report to produce this output. The source retains its original whitespace:
First-seen: {sql=3, java=2, css=1}
Sorted keys: {css=1, java=2, sql=3}
Unique words: 3
Original: [sql, Java , SQL, css, java, sql]
Main.java · Count normalized tokens
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints
Stage 1: validate and normalize. Check the list reference first, then each token reference before calling strip. Locale.ROOT avoids dependence on the machine’s default locale.
Stage 2 hints
Stage 2: count into a new LinkedHashMap. For each normalized word, read getOrDefault(word, 0), add one, and store the new count. Never insert null values.
Stage 3 hints
Stage 3: keep display policies separate. Return the first-seen map. Create a new TreeMap from it for sorted-key output, then compare key iteration as a List in your tests.
Reveal the complete explained solution
Main.java · Preserve first-seen order and show sorted keys
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The first sql establishes the first key position and finishes with count three. Java and java normalize to the same second key and total two; css totals one. All writes target a new map, so the original spacing and sequence remain unchanged even after a validation failure. Each occurrence contributes one; size counts unique keys, not total occurrences. With a stable List whose size fits int, a per-word count cannot exceed that size. The helper is not designed for concurrent source mutation or unbounded streaming input.
Test your contract
- Empty input gives an empty editable map; one token gives count one.
- Separated and adjacent repeats accumulate rather than replace the count with one.
- Verify first-seen order with new ArrayList<>(result.keySet()), not only Map.equals.
- Reject null and blank input without changing the original list; keep punctuation attached.
- Under a Turkish default locale, I still normalizes with Locale.ROOT; the sum of counts equals the valid input size.
To verify encounter sequence, compare new ArrayList<>(counts.keySet()) with the expected keys. For null-entry tests, add null to an ArrayList explicitly: List.of rejects it before your helper runs. The tokens java and java! remain separate because punctuation removal is not part of this contract.
Common mistakes and how to diagnose them
A duplicate key did not create another row
A map has one mapping per key. put replaces the current value and returns its predecessor. Use a count or a list as the value when your domain needs multiple occurrences.
getOrDefault still returned null
The key may be present with a null value. Default lookup only handles absence. Inspect containsKey or enforce a non-null-value invariant before arithmetic.
A sorted map is not sorted by frequency
TreeMap orders keys. Sorting entries by numeric value is a separate task with a tie policy, covered alongside comparators later. Do not describe key ordering as value ranking.
Removing a displayed key changed the map
keySet is backed by its map. Copy it into a new set when you need an independent container. A shallow copy still shares element references.
A counter keeps returning one
Writing put(word, 1) on every occurrence overwrites the prior count. Read the previous non-null count, increment it, then store the update under the normalized key.
A passing equality assertion hid a display-order bug
Map.equals compares mappings rather than their encounter sequence. Check a List of traversed keys too when first-seen presentation is part of the contract.
Frequently asked questions
Can values repeat if keys cannot?
Yes. Several distinct keys can map to equal values. A map’s size counts keys/mappings, not distinct values. Use the key type to express lookup identity and the value type to express the associated data.
Is Map a Collection?
No. Map belongs to the broader framework but does not extend Collection. keySet, values, and entrySet expose collection views for traversal. values is a Collection because values may repeat.
Does getOrDefault insert the fallback?
No. It returns a stored value or, for an absent key, the fallback. A later put is still needed in the beginner counting loop. An explicit null mapping remains null.
Does TreeMap sort by the largest count?
No. It orders keys by natural ordering or a supplied key comparator. Ranking words by frequency requires sorting entries by value and defining how equal counts should be ordered.
Can this lab accept an entire paragraph as one String?
It can count that String as a single token, but it does not parse words from it. The input is already tokenized. Punctuation removal, Unicode normalization, and language-aware word segmentation require their own explicit policy.
Should I replace every map with Map.of?
No. Map.of is an unmodifiable factory that rejects null keys/values and duplicate keys and does not promise first-seen iteration order. It cannot replace the editable, insertion-ordered counter promised by this lab.
Choose keys, missing-data rules, and ordering deliberately
A map keeps one mapping per key, while values may repeat. get can be ambiguous around null; getOrDefault is not insertion. Entry traversal exposes both parts, views stay connected to their owner, and copies separate structural edits. TreeMap sorts keys, not counts.
Before moving on, explain why a missing key caused the repair failure, why the lab counts six occurrences but only three keys, and why a TreeMap copy does not replace the first-seen report. Next: iterators and collection algorithms.
Can you read and update a map safely?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
This original SovranCode lesson uses the official Java 21 APIs to verify key/value contracts and implementation behavior.
Review sets · Review typed collections · Java course roadmap.
Last updated: