This collection of 25 Java exercises covers the full Map family: HashMap, TreeMap, LinkedHashMap, and how they compare in ordering, performance, and use case.
You’ll start with basic CRUD operations, iteration styles, and useful shortcut methods like merge() and computeIfAbsent(), then move into TreeMap-specific navigation methods (floorKey(), ceilingKey(), subMap(), descendingMap()), LinkedHashMap‘s insertion and access ordering (including a hand-built LRU cache), grouping and sorting with the Stream API, and closing out with a look at why plain HashMap isn’t thread-safe.
Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation, so the reasoning behind each method choice is just as clear as the code itself.
- Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
- Practice questions using our Online Java Compiler
+ Table of Contents (25 Exercises)
Table of contents
- Exercise 1: HashMap Basic Operations
- Exercise 2: Iterating a HashMap
- Exercise 3: Word Frequency Counter
- Exercise 4: Merging Two HashMaps
- Exercise 5: Default Value with computeIfAbsent()
- Exercise 6: hashCode() and equals() in Custom Keys
- Exercise 7: Finding Max and Min Values
- Exercise 8: Inverting a Map
- Exercise 9: TreeMap Natural Ordering
- Exercise 10: TreeMap with a Custom Comparator
- Exercise 11: First and Last Entry in a TreeMap
- Exercise 12: floorKey() and ceilingKey()
- Exercise 13: lowerKey() and higherKey()
- Exercise 14: Extracting a Range with subMap()
- Exercise 15: Splitting a Map with headMap() and tailMap()
- Exercise 16: Reversing a TreeMap with descendingMap()
- Exercise 17: HashMap vs LinkedHashMap Order
- Exercise 18: LinkedHashMap Access Order
- Exercise 19: Building an LRU Cache
- Exercise 20: First and Last Key in LinkedHashMap
- Exercise 21: Updating a Value Without Changing Order
- Exercise 22: Grouping Objects with Streams
- Exercise 23: Converting HashMap to TreeMap
- Exercise 24: Sorting a Map by Value
- Exercise 25: Thread Safety: HashMap vs ConcurrentHashMap
Exercise 1: HashMap Basic Operations
Problem Statement: Create a HashMap that stores the names of 5 cities (Keys) and their populations (Values). Practice adding, retrieving, updating, and removing entries.
Purpose: This exercise helps you practice the four fundamental operations on a HashMap, adding new entries, reading a value by its key, updating an existing value, and removing an entry entirely.
Given Input: Cities: Delhi, Mumbai, Chennai, Kolkata, Pune with their respective populations
Population of Mumbai: 20411000
Updated population of Pune: 7500000
After removing Kolkata: {Delhi=32900000, Mumbai=20411000, Chennai=10971000, Pune=7500000}
▼ Hint
- Use
put(key, value)five times to add the initial cities and populations. - Use
get(key)to retrieve a single population by city name. - Call
put()again with an existing key to overwrite its value, which is how updates work in aHashMap. - Use
remove(key)to delete an entry completely.
▼ Solution & Explanation
Explanation:
cityPopulation.put("Delhi", 32900000): Adds a new key-value pair to the map, or overwrites the value if the key already exists.cityPopulation.get("Mumbai"): Looks up the value associated with the given key and returnsnullif the key is not present.cityPopulation.put("Pune", 7500000): Since"Pune"already exists as a key, this replaces its old value instead of creating a duplicate entry.cityPopulation.remove("Kolkata"): Deletes the entry for"Kolkata"entirely, reducing the map’s size by one.
Exercise 2: Iterating a HashMap
Problem Statement: Write a program that iterates through a HashMap using four different methods: entrySet(), keySet(), the forEach() loop, and an Iterator.
Purpose: This exercise surveys the different ways Java allows you to traverse a map, helping you recognize each style in existing code and choose the most appropriate one for a given situation.
Given Input: Map: {"A"=1, "B"=2, "C"=3}
Using entrySet: A=1 Using entrySet: B=2 Using entrySet: C=3 Using keySet: A -> 1 Using keySet: B -> 2 Using keySet: C -> 3 Using forEach: A = 1 Using forEach: B = 2 Using forEach: C = 3 Using Iterator: A=1 Using Iterator: B=2 Using Iterator: C=3
▼ Hint
- Loop over
map.entrySet()with a for-each loop, reading both the key and value directly from eachMap.Entry. - Loop over
map.keySet(), then callmap.get(key)inside the loop to fetch each value separately. - Call
map.forEach((key, value) -> ...)and pass a lambda that receives both parameters directly. - Get an
Iteratorfrommap.entrySet().iterator()and usehasNext()andnext()to walk through the entries manually.
▼ Solution & Explanation
Explanation:
map.entrySet(): Returns a view of all key-value pairs together asMap.Entryobjects, avoiding a second lookup to fetch the value.map.keySet(): Returns only the keys, requiring a separateget()call inside the loop if the value is also needed.map.forEach((key, value) -> ...): Accepts a lambda expression that receives each key and value pair directly, without an explicit loop construct.map.entrySet().iterator(): Gives explicit control over traversal, which is useful when entries need to be removed safely during iteration usingiterator.remove().
Exercise 3: Word Frequency Counter
Problem Statement: Given a long paragraph of text, write a program that counts the frequency of each unique word using a HashMap and prints the results.
Purpose: This exercise practices a very common map pattern, accumulating counts per key while scanning through a data source, which is the foundation of frequency analysis and basic text processing.
Given Input: String text = "the quick fox jumps over the lazy fox the fox runs";
the: 3 quick: 1 fox: 3 jumps: 1 over: 1 lazy: 1 runs: 1
▼ Hint
- Split the text into individual words using
text.split(" "). - For each word, use
map.getOrDefault(word, 0)to read the current count, defaulting to0if the word has not been seen yet. - Add
1to that value and store it back into the map usingput().
▼ Solution & Explanation
Explanation:
text.split(" "): Breaks the sentence into an array of individual words based on spaces.wordCount.getOrDefault(word, 0): Returns the word’s current count if it exists, or0if this is the first time the word has appeared, avoiding a separatecontainsKey()check.wordCount.put(word, ... + 1): Writes the incremented count back into the map, either updating an existing entry or creating a new one.
Exercise 4: Merging Two HashMaps
Problem Statement: Create two separate HashMap objects containing employee IDs and their department names. Merge the second map into the first map. If an ID exists in both, append "-Dual" to the department name using Map.merge().
Purpose: This exercise introduces merge(), a method designed specifically for combining maps where the caller decides exactly what should happen when the same key appears in both sources.
Given Input: Map1: {101=Engineering, 102=Sales}, Map2: {102=Marketing, 103=HR}
Expected Output: {101=Engineering, 102=Sales-Dual, 103=HR}
▼ Hint
- Loop through every entry in the second map.
- For each entry, call
map1.merge(id, department, (oldVal, newVal) -> oldVal + "-Dual"). - If the key does not yet exist in
map1,merge()simply inserts the new value directly, without ever calling the merging function.
▼ Solution & Explanation
Explanation:
map1.merge(key, value, function): Ifkeyis not already present, it simply insertsvaluedirectly, just likeput().(oldVal, newVal) -> oldVal + "-Dual": Runs only when the key already exists, receiving the existing value asoldValand the incoming value asnewVal, and returning the combined result to store.102becomes"Sales-Dual": Since ID102exists in both maps, the merge function appends"-Dual"to the original department name rather than simply overwriting it.
Exercise 5: Default Value with computeIfAbsent()
Problem Statement: Write a program that attempts to look up a stock price in a map. If the stock isn’t listed, use computeIfAbsent() to fetch a default price and add it to the map automatically.
Purpose: This exercise introduces computeIfAbsent(), which combines a lookup and a conditional insert into a single call, useful for lazily populating a map only when a key is first requested.
Given Input: Map: {"AAPL"=190.0, "TSLA"=250.0}, lookup: "GOOG"
Price of GOOG: 100.0
Map after lookup: {AAPL=190.0, TSLA=250.0, GOOG=100.0}
▼ Hint
- Call
stockPrices.computeIfAbsent("GOOG", key -> fetchDefaultPrice(key)). - If
"GOOG"is already a key, the lambda never runs and the existing value is simply returned. - If
"GOOG"is missing, the lambda runs, and whatever it returns is both inserted into the map and returned to the caller in the same step.
▼ Solution & Explanation
Explanation:
stockPrices.computeIfAbsent("GOOG", key -> 100.0): Checks whether"GOOG"is already present. Since it is not, the lambda runs and produces100.0as the default price.- Automatic insertion: The value returned by the lambda is stored into the map under the key
"GOOG"as part of the same call, no separateput()is needed. - Existing key behavior: If
"AAPL"had been looked up instead, the lambda would never execute, and the stored value190.0would simply be returned unchanged.
Exercise 6: hashCode() and equals() in Custom Keys
Problem Statement: Create a custom User class without overriding hashCode() and equals(). Add two identical User objects as keys to a HashMap. Observe the behavior, then fix the class by properly overriding both methods to see how the map’s behavior changes.
Purpose: This exercise demonstrates why HashMap relies on hashCode() and equals() to determine whether two keys are the same, and what goes wrong when a custom class does not define them properly.
Given Input: User u1 = new User("neha_k"); User u2 = new User("neha_k");
Without equals/hashCode, map size: 2 With equals/hashCode, map size: 1
▼ Hint
- Without overriding anything,
Userinherits the defaulthashCode()andequals()fromObject, which compare objects by memory reference rather than content. - Two separately constructed
Userobjects with the sameusernamewill therefore be treated as different keys, even though they look identical. - After overriding
equals()to compareusernamefields andhashCode()to be based on the same field, the map will correctly treat the two objects as the same key.
▼ Solution & Explanation
Explanation:
UserWithoutOverride: Relies on the defaultObjectimplementation, which treats every new object as a distinct key, so the map ends up with two separate entries despite the identical usernames.public boolean equals(Object obj): Defines what it means for twoUserWithOverrideobjects to be considered equal, comparing theirusernamefields instead of their memory addresses.Objects.hash(username): Produces a hash code based on the same field used inequals(), which is required sinceHashMapuseshashCode()first to locate the correct bucket before checkingequals().- Result: The second map correctly recognizes the two
UserWithOverrideobjects as the same key, so the secondput()overwrites the first, leaving a size of1.
Exercise 7: Finding Max and Min Values
Problem Statement: Given a HashMap<String, Integer> representing student names and their exam scores, find and print the key (student) with the highest score and the key with the lowest score without sorting the map.
Purpose: This exercise practices scanning a map’s entries in a single pass to track the running maximum and minimum, avoiding the unnecessary overhead of sorting the entire map just to find two values.
Given Input: Map: {"Amit"=78, "Priya"=92, "Ravi"=65, "Sneha"=88}
Highest scorer: Priya with 92 Lowest scorer: Ravi with 65
▼ Hint
- Initialize two variables to track the current highest and lowest names, starting from any single entry in the map.
- Loop through
map.entrySet()once, comparing each entry’s score against the current tracked highest and lowest. - Update the tracked variables whenever a new highest or lowest score is found during the scan.
▼ Solution & Explanation
Explanation:
Integer.MIN_VALUEandInteger.MAX_VALUE: Used as starting points that any real score will beat, guaranteeing the first comparison always updates the tracked values.if (entry.getValue() > highestScore): Updates the tracked highest student and score only when the current entry beats everything seen so far.- Single pass: Both the highest and lowest are found while scanning the entries exactly once, which is more efficient than sorting the entire map first.
Exercise 8: Inverting a Map
Problem Statement: Write a method that takes a HashMap<K, V> and returns a new HashMap<V, List<K>>, effectively inverting the map so that duplicate values aggregate their corresponding keys.
Purpose: This exercise practices building a new map structure from an existing one, and specifically handles the case where the original values are not unique, meaning several keys must be grouped together under one inverted key.
Given Input: Map: {"apple"="fruit", "carrot"="vegetable", "banana"="fruit", "potato"="vegetable"}
Expected Output: {fruit=[apple, banana], vegetable=[carrot, potato]}
▼ Hint
- Create a new empty
HashMap<V, List<K>>to hold the inverted result. - Loop through the original map’s entries, and for each one, use
computeIfAbsent(value, k -> new ArrayList<>())to get or create the list associated with that value. - Add the original key to that list, so every key that shared the same value ends up grouped together.
▼ Solution & Explanation
Explanation:
inverted.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()): Ensures a list exists for the current value, creating an empty one the first time that value is encountered..add(entry.getKey()): Appends the original key to the list associated with its value, so entries like"apple"and"banana"both end up under"fruit".- Generic method: Declaring the method as
static <K, V>lets it work with a map of any key and value types, not justStringpairs.
Exercise 9: TreeMap Natural Ordering
Problem Statement: Create a TreeMap of product IDs (Strings) and prices (Doubles). Insert elements out of order and print the map to demonstrate how it automatically sorts keys alphabetically.
Purpose: This exercise introduces TreeMap, which keeps its keys in sorted order at all times, unlike HashMap, which makes no guarantee about iteration order.
Given Input: Insert order: "P300", "P100", "P200"
Expected Output: {P100=45.0, P200=30.0, P300=99.5}
▼ Hint
- Insert the three product entries in the exact order shown, without worrying about their final position.
- Simply print the
TreeMapdirectly. - Unlike a
HashMap, aTreeMapalways iterates its keys in their natural sorted order, which forStringkeys means alphabetical order.
▼ Solution & Explanation
Explanation:
productPrices.put("P300", 99.5): Insertion order does not matter to aTreeMap, so entries can be added in any sequence.- Internal structure: A
TreeMapstores its entries in a sorted tree structure rather than the bucket-based structure aHashMapuses, which is what keeps the keys ordered automatically. - Result: Printing the map shows the keys in alphabetical order,
P100,P200, thenP300, regardless of the order they were inserted.
Exercise 10: TreeMap with a Custom Comparator
Problem Statement: Create a TreeMap that stores employee names and salaries, but configure it via a custom Comparator to sort the names in reverse alphabetical order.
Purpose: This exercise practices supplying a custom Comparator to a TreeMap‘s constructor, showing how the natural sort order can be overridden entirely with your own comparison rule.
Given Input: Insert order: "Aman", "Divya", "Karan"
Expected Output: {Karan=55000.0, Divya=62000.0, Aman=48000.0}
▼ Hint
- Pass a
Comparator<String>into theTreeMapconstructor instead of leaving it empty. - Use
Comparator.reverseOrder(), or a lambda like(a, b) -> b.compareTo(a), to reverse the default alphabetical comparison. - Every subsequent
put()call will be positioned according to this custom ordering rather than natural ordering.
▼ Solution & Explanation
Explanation:
new TreeMap<>(Comparator.reverseOrder()): Passes a customComparatorinto the constructor, telling theTreeMapto use reverse alphabetical order instead of its default natural ordering.Comparator.reverseOrder(): A built-in comparator that reverses whatever the natural ordering of the type would otherwise be, which forStringmeans Z before A.- Result: Regardless of the order the three names were inserted, printing the map shows
Karan,Divya, thenAman, following the reversed alphabetical rule.
Exercise 11: First and Last Entry in a TreeMap
Problem Statement: Given a TreeMap of timestamps (Long) and system logs (String), write code to find the exact first and last entries in the map using firstEntry() and lastEntry().
Purpose: This exercise introduces the direct entry-retrieval methods that TreeMap offers thanks to its sorted structure, giving instant access to the smallest and largest keyed entries without scanning the whole map.
Given Input: Map: {1000=Server started, 3000=User login, 2000=Cache cleared, 5000=Server stopped}
First entry: 1000=Server started Last entry: 5000=Server stopped
▼ Hint
- Call
logs.firstEntry(), which returns the entry with the smallest key as aMap.Entry. - Call
logs.lastEntry(), which returns the entry with the largest key. - Both methods return
nullif the map is empty, so no manual scanning or sorting is needed.
▼ Solution & Explanation
Explanation:
logs.firstEntry(): Because the underlying tree keeps keys sorted, the smallest key is always the leftmost node, so this method can return it directly without a search.logs.lastEntry(): Similarly returns the rightmost node in the tree, which holds the largest key.- Insertion order irrelevant: Even though
3000Lwas inserted before2000L, the sorted structure ofTreeMapensuresfirstEntry()andlastEntry()always reflect the true minimum and maximum keys.
Exercise 12: floorKey() and ceilingKey()
Problem Statement: You have a TreeMap<Integer, String> representing coupon codes and their required minimum purchase amounts. Use floorKey() and ceilingKey() to find the best available coupon for a user spending exactly $75.
Purpose: This exercise introduces floorKey() and ceilingKey(), which locate the closest key at or below, and at or above, a given value, a common need in tiered pricing and threshold based logic.
Given Input: Map: {20="WELCOME5", 50="SAVE10", 75="SAVE15", 100="SAVE20", 150="SAVE30"}, spend = 75
Floor key for 75: 75 Ceiling key for 75: 75
▼ Hint
- Call
coupons.floorKey(75), which returns the largest key that is less than or equal to75. - Call
coupons.ceilingKey(75), which returns the smallest key that is greater than or equal to75. - Since
75itself is an exact key in the map, both methods return75directly, matching the coupon exactly.
▼ Solution & Explanation
Explanation:
coupons.floorKey(75): Searches for the largest key not exceeding75. Since75is itself present as a key, it is returned exactly.coupons.ceilingKey(75): Searches for the smallest key not smaller than75, which is also75in this case.- When there is no exact match: If the user had spent
80instead,floorKey(80)would return75andceilingKey(80)would return100, showing how the two methods diverge once the key falls between two actual entries.
Exercise 13: lowerKey() and higherKey()
Problem Statement: Using the same coupon map from the previous exercise, use lowerKey() and higherKey() to find coupons strictly below or strictly above a $75 threshold.
Purpose: This exercise contrasts lowerKey() and higherKey() with floorKey() and ceilingKey(), highlighting that these variants exclude an exact match, which matters whenever the boundary value itself must not count.
Given Input: Map: {20="WELCOME5", 50="SAVE10", 75="SAVE15", 100="SAVE20", 150="SAVE30"}, threshold = 75
Lower key than 75: 50 Higher key than 75: 100
▼ Hint
- Call
coupons.lowerKey(75), which finds the largest key strictly less than75, skipping over75itself even though it exists in the map. - Call
coupons.higherKey(75), which finds the smallest key strictly greater than75. - Compare these results to
floorKey(75)andceilingKey(75)from the previous exercise, both of which returned75directly.
▼ Solution & Explanation
Explanation:
coupons.lowerKey(75): Even though75exists in the map, this method deliberately excludes it and returns50, the next key down.coupons.higherKey(75): Skips past75as well, returning100, the next key up.- Key distinction: The
lowerandhigherfamily of methods never return the exact key you pass in, whereasfloorandceilingwill if that key is actually present.
Exercise 14: Extracting a Range with subMap()
Problem Statement: Create a TreeMap representing a schedule (Key: hour as an Integer from 0 to 23, Value: task name). Use subMap() to extract and print all tasks scheduled between 9 AM (inclusive) and 5 PM (exclusive).
Purpose: This exercise practices extracting a contiguous range of entries from a sorted map using subMap(), which is far more efficient than manually filtering every entry with an if check.
Given Input: Map: {8="Breakfast", 9="Standup", 12="Lunch", 14="Client Call", 17="Wrap-up", 20="Dinner"}
{9=Standup, 12=Lunch, 14=Client Call}
▼ Hint
- Call
schedule.subMap(9, 17), which returns a view containing all entries with keys from9up to but not including17. - This matches the requirement of 9 AM inclusive and 5 PM exclusive exactly, since
17represents 5 PM in 24 hour format. - The returned
subMap()is a live view backed by the original map, so changes to it would also affect the original.
▼ Solution & Explanation
Explanation:
schedule.subMap(9, 17): Extracts every entry whose key falls in the range starting at9and ending just before17, taking advantage of the sorted structure to do this efficiently.- Inclusive start, exclusive end: The two-argument version of
subMap()always includes the start key and excludes the end key, which is why8and17are absent from the result but9is present. SortedMap<Integer, String>:subMap()returns aSortedMapview rather than a fresh independent copy, which is why the return type is declared as the more general interface.
Exercise 15: Splitting a Map with headMap() and tailMap()
Problem Statement: Using a TreeMap of inventory items and stock levels, use headMap() to get all items with IDs lexicographically before a certain point, and tailMap() to get everything after.
Purpose: This exercise practices splitting a sorted map into two complementary halves using headMap() and tailMap(), both of which rely on the map’s natural key ordering to define the split point.
Given Input: Map: {"A100"=50, "B200"=30, "C150"=80, "D300"=20}
Head (before C150): {A100=50, B200=30}
Tail (from C150): {C150=80, D300=20}
▼ Hint
- Call
inventory.headMap("C150"), which returns every entry with a key strictly less than"C150", in lexicographical order. - Call
inventory.tailMap("C150"), which returns every entry with a key greater than or equal to"C150". - Together,
headMap()andtailMap()at the same split point always account for every entry in the original map exactly once.
▼ Solution & Explanation
Explanation:
inventory.headMap("C150"): Returns everything lexicographically before"C150", which excludes"C150"itself.inventory.tailMap("C150"): Returns everything from"C150"onward, including"C150"itself, which is why it appears in the tail rather than the head.- Complementary split: Every original entry appears in exactly one of the two resulting maps, with no overlap and no entry left out.
Exercise 16: Reversing a TreeMap with descendingMap()
Problem Statement: Take an existing, populated TreeMap and create a reverse-order view of it using the descendingMap() method.
Purpose: This exercise introduces descendingMap(), which flips the iteration order of a TreeMap entirely, useful whenever you need the largest keys first without rebuilding the map manually.
Given Input: Map: {1=One, 2=Two, 3=Three, 4=Four}
Expected Output: {4=Four, 3=Three, 2=Two, 1=One}
▼ Hint
- Call
numbers.descendingMap(), which returns aNavigableMapview of the same entries but in reverse key order. - Print the returned map directly to see the entries appear from highest key to lowest.
- This view is backed by the original map, so it always reflects the current state of
numbers, it does not create a separate copy.
▼ Solution & Explanation
Explanation:
numbers.descendingMap(): Produces a view where iterating the entries visits them from the highest key down to the lowest, the exact opposite of the map’s normal order.NavigableMap<Integer, String>: The return type supports the same range and navigation methods asTreeMapitself, just operating in the reversed direction.- Live view: Because
reversedis backed bynumbers, any future changes made tonumberswould automatically be reflected whenreversedis printed again.
Exercise 17: HashMap vs LinkedHashMap Order
Problem Statement: Insert 5 random pairs into a HashMap and the exact same 5 pairs into a LinkedHashMap. Print both maps to visually contrast the unpredictable order of HashMap against the strict insertion order of LinkedHashMap.
Purpose: This exercise puts HashMap and LinkedHashMap side by side using identical data, making the ordering guarantee that LinkedHashMap provides, and that HashMap does not, directly visible.
Given Input: Insert order: "Zebra", "Apple", "Mango", "Banana", "Kiwi"
HashMap order (unpredictable): {Banana=4, Apple=2, Kiwi=5, Zebra=1, Mango=3}
LinkedHashMap order (insertion): {Zebra=1, Apple=2, Mango=3, Banana=4, Kiwi=5}
▼ Hint
- Insert the exact same five key-value pairs, in the exact same order, into both a
HashMapand aLinkedHashMap. - Print each map directly afterward.
- The
HashMap‘s printed order depends on each key’s hash code and internal bucket placement, and is not guaranteed to match insertion order, while theLinkedHashMapalways preserves it.
▼ Solution & Explanation
Explanation:
HashMapordering: Entries are placed into internal buckets based on each key’s hash code, so the printed order can differ from insertion order and may even change between different Java versions or runs.LinkedHashMapordering: Internally maintains a doubly linked list connecting the entries in the order they were inserted, so iterating or printing it always reproduces that exact insertion sequence.- Same data, different guarantees: Both maps hold identical key-value pairs, but only one of them makes any promise about the order those pairs will be visited in.
Exercise 18: LinkedHashMap Access Order
Problem Statement: Initialize a LinkedHashMap with its access-order flag set to true. Insert 4 entries, access the second entry twice, and print the map to see how the accessed entry automatically jumps to the end of the iteration order.
Purpose: This exercise introduces the access-order mode of LinkedHashMap, which reorders entries based on when they were last read rather than when they were inserted, forming the basis of simple LRU cache designs.
Given Input: Insert order: "A", "B", "C", "D"; then get("B") is called twice
Expected Output: {A=1, C=3, D=4, B=2}
▼ Hint
- Use the three-argument
LinkedHashMapconstructor,new LinkedHashMap<>(initialCapacity, loadFactor, true), where the finaltrueenables access order. - Insert the four entries in order, then call
map.get("B")twice. - In access-order mode, every successful
get()call moves that entry to the end of the iteration order, so"B"ends up last even though it was originally inserted second.
▼ Solution & Explanation
Explanation:
new LinkedHashMap<>(16, 0.75f, true): The finaltrueswitches the map from its default insertion-order mode into access-order mode.map.get("B"): Every time this runs successfully, the entry for"B"is moved to the end of the internal linked list, marking it as the most recently used.- Result: Even though
"B"was the second entry inserted, callingget("B")twice pushes it to the very end of the printed order, ahead of nothing else since it is now the most recently accessed.
Exercise 19: Building an LRU Cache
Problem Statement: Extend LinkedHashMap to create a simple LRU (Least Recently Used) Cache with a maximum capacity of 3 items. Override removeEldestEntry() so that the oldest, least-accessed item is automatically evicted when a 4th item is added.
Purpose: This exercise combines access-order mode with removeEldestEntry() to build a genuine fixed-size cache, showing how LinkedHashMap was specifically designed to support this exact pattern.
Given Input: Insert order: put(1,"A"), put(2,"B"), put(3,"C"), get(1), put(4,"D")
Expected Output: {3=C, 1=A, 4=D}
▼ Hint
- Create a class that extends
LinkedHashMap<K, V>, callingsuper(capacity, 0.75f, true)in its constructor to enable access order. - Override
removeEldestEntry(Map.Entry<K, V> eldest)to returntruewheneversize() > capacity. - This method is automatically called by
LinkedHashMapinternally right after everyput(), and returningtruetells it to remove the eldest entry immediately.
▼ Solution & Explanation
Explanation:
super(capacity, 0.75f, true): Enables access order in the parentLinkedHashMap, which is required for the cache to correctly identify the least recently used entry.removeEldestEntry(Map.Entry<K, V> eldest): Runs automatically after every insertion, receiving the entry that would be evicted if this method returnstrue.cache.get(1): Marks key1as recently used, moving it away from being the eldest entry, which is why key2is evicted instead whenput(4, "D")pushes the cache over capacity.
Exercise 20: First and Last Key in LinkedHashMap
Problem Statement: Write a method for a standard insertion-order LinkedHashMap that retrieves the very first (oldest inserted) element and the very last (most recently inserted) element without looping through the entire collection.
Purpose: This exercise practices reaching directly for the boundary elements of an ordered map using its iterator and array conversion, instead of writing a manual scan that tracks the first and last values seen.
Given Input: Insert order: "First", "Second", "Third", "Fourth"
Oldest key: First Newest key: Fourth
▼ Hint
- Get the oldest key using
map.keySet().iterator().next(), which grabs the very first key without visiting any of the others. - Convert the key set to an array with
map.keySet().toArray(), then read the newest key using the last index,array[array.length - 1]. - Because
LinkedHashMappreserves insertion order, the first key produced by its iterator is always the oldest entry, and the last position in the array is always the newest.
▼ Solution & Explanation
Explanation:
map.keySet().iterator().next(): Requests only a single element from the iterator, stopping immediately after the very first key rather than continuing through the rest.map.keySet().toArray(): Copies the keys into an array in their current insertion order, letting the last element be reached directly by index.keys[keys.length - 1]: Since the array mirrors the map’s insertion order, this index always points at the most recently inserted key.
Exercise 21: Updating a Value Without Changing Order
Problem Statement: Given a LinkedHashMap, update the value of an existing key. Verify whether updating the value alters the element’s original position in the insertion order.
Purpose: This exercise clarifies a subtle but important distinction in a standard insertion-order LinkedHashMap, that overwriting a value through put() does not move the entry, unlike the access-order mode explored in earlier exercises.
Given Input: Map: {"A"=1, "B"=2, "C"=3}, update: put("A", 100)
Expected Output: {A=100, B=2, C=3}
▼ Hint
- Create a regular
LinkedHashMapwithout passing the access-order flag, so it defaults to insertion order. - Call
put("A", 100)again on the existing key"A". - Print the map afterward and check whether
"A"is still first, confirming that a value update alone does not affect ordering in the default mode.
▼ Solution & Explanation
Explanation:
new LinkedHashMap<>(): Without the three-argument constructor used in earlier access-order exercises, this map defaults to preserving insertion order only.map.put("A", 100): Since"A"already exists, this overwrites its value in place without removing and reinserting the entry.- Result:
"A"remains the first entry in the printed map, confirming that a value update by itself never changes an entry’s position in insertion-order mode.
Exercise 22: Grouping Objects with Streams
Problem Statement: Given a List of Product objects (each having a category, name, and price), use Java Streams to group them into a Map<String, List<Product>> based on their category.
Purpose: This exercise practices Collectors.groupingBy(), a Stream API method that builds a grouped map directly from a list in a single expression, replacing what would otherwise require a manual loop and computeIfAbsent().
Given Input: Products: ("Laptop", "Electronics", 999), ("Shirt", "Clothing", 29), ("Phone", "Electronics", 599), ("Jeans", "Clothing", 49)
Electronics: [Laptop, Phone] Clothing: [Shirt, Jeans]
▼ Hint
- Give the
Productclass fields forname,category, andprice, along with a getter for each. - Call
products.stream().collect(Collectors.groupingBy(Product::getCategory))to build the grouped map in one line. - Each key in the resulting map is a distinct category, and each value is a
List<Product>containing every product that shares that category.
▼ Solution & Explanation
Explanation:
products.stream(): Converts theList<Product>into a stream so it can be processed with the Stream API.Collectors.groupingBy(Product::getCategory): Groups every product by the result of callinggetCategory()on it, automatically creating a new list for each distinct category encountered.Product::getCategory: A method reference that tells the collector which field to group by, equivalent to writingp -> p.getCategory().
Exercise 23: Converting HashMap to TreeMap
Problem Statement: Start with a chaotic, unsorted HashMap. Write a single line of code (or a brief method) to convert it into a TreeMap so that all existing elements become instantly sorted by key.
Purpose: This exercise practices converting between map implementations using a constructor call, showing that any Map can be handed directly to a TreeMap constructor to produce a sorted copy in one step.
Given Input: HashMap: {"Zebra"=1, "Apple"=2, "Mango"=3}
Expected Output: {Apple=2, Mango=3, Zebra=1}
▼ Hint
Pass the existing HashMap directly into the TreeMap constructor, new TreeMap<>(unsortedMap), which copies every entry and immediately arranges them according to natural key order.
▼ Solution & Explanation
Explanation:
new TreeMap<>(unsortedMap):TreeMaphas a constructor that accepts anyMap, copying its entries in and sorting them according to their natural ordering as part of construction.- Independent copy:
sortedMapis a completely separate object, so further changes tounsortedMapafterward would not affect the sorted copy. - Result: Printing
sortedMapshows the same three entries as the originalHashMap, but now arranged alphabetically by key.
Exercise 24: Sorting a Map by Value
Problem Statement: Because maps sort by keys by default, write a program that takes a HashMap<String, Integer> and sorts it based entirely on its values in ascending order, returning a new LinkedHashMap to preserve that sorted state.
Purpose: This exercise practices sorting map entries by value using the Stream API, then rebuilding the result into a LinkedHashMap, the only common map type capable of preserving a specific, manually determined order.
Given Input: Map: {"Charlie"=85, "Alice"=92, "Bob"=70}
Expected Output: {Bob=70, Charlie=85, Alice=92}
▼ Hint
- Call
map.entrySet().stream()to begin working with the entries as a stream. - Use
.sorted(Map.Entry.comparingByValue())to sort the stream by each entry’s value instead of its key. - Collect the sorted stream into a
LinkedHashMapusingCollectors.toMap()with a merge function andLinkedHashMap::newas the map supplier, since only aLinkedHashMapwill remember the order the entries were inserted in.
▼ Solution & Explanation
Explanation:
Map.Entry.comparingByValue(): A ready-made comparator that ordersMap.Entryobjects by their value in ascending order, avoiding the need to write a custom comparator by hand.Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new): Rebuilds a map from the sorted stream, pulling the key and value from each entry, using(a, b) -> ato resolve any duplicate keys, and specifically constructing aLinkedHashMapto preserve the sorted stream order.- Why
LinkedHashMap: A plainHashMapwould immediately scramble the carefully sorted order again, since it has no concept of insertion order, which is why the map supplier matters here.
Exercise 25: Thread Safety: HashMap vs ConcurrentHashMap
Problem Statement: Create a regular HashMap and attempt to update it simultaneously from three different threads. Observe the potential for a ConcurrentModificationException or data loss. Then, resolve the issue using Collections.synchronizedMap() or discuss how ConcurrentHashMap solves this more efficiently.
Purpose: This exercise demonstrates why HashMap is unsafe under concurrent writes, then compares two standard fixes, wrapping a map with a single lock versus using a map designed from the ground up for concurrent access.
Given Input: Three threads each inserting 1000 unique key-value pairs into the same map at the same time.
Unsafe HashMap final size: 1000 (unreliable, may vary or throw an exception between runs) Synchronized HashMap final size: 1000 (reliable) ConcurrentHashMap final size: 1000 (reliable)
▼ Hint
- Create a plain
HashMapand start threeThreadobjects that all callput()on it at the same time, then calljoin()on all three before checking the final size. - Because
HashMapperforms no internal locking, concurrent writes can corrupt its internal bucket structure, occasionally causing lost entries, incorrect sizes, or even an infinite loop during a resize. - Wrap a
HashMapwithCollections.synchronizedMap(new HashMap<>())to make every operation acquire the same single lock before proceeding, which is safe but forces all threads to wait on each other. - Use a
ConcurrentHashMapinstead for a thread-safe map that allows multiple threads to write to different parts of the map at the same time, rather than blocking on one single lock.
▼ Solution & Explanation
Explanation:
- Unsafe
HashMap: All three threads write to the same underlying bucket array with no coordination, so a resize operation triggered by one thread can corrupt the structure another thread is simultaneously reading or writing, occasionally producing a wrong size, lost entries, or in rare cases an infinite loop. Collections.synchronizedMap(new HashMap<>()): Wraps the map so every method call acquires a single shared lock first, guaranteeing correctness, but forcing every thread to wait its turn even when writing to completely unrelated keys.ConcurrentHashMap: Achieves thread safety without a single global lock, internally dividing its structure so that operations on different parts of the map can proceed simultaneously, which is why it is generally recommended oversynchronizedMap()for concurrent workloads.- t1.join(); t2.join(); t3.join();: Ensures the main thread waits for all three worker threads to fully finish inserting before the final size is printed, so the result reflects the completed work rather than a partial state.

Leave a Reply