Java Map Interface — Key-Value Collections
A Map stores key-value pairs where each key is unique. Think of it as a dictionary — look up a word (key) to find its definition (value).
The Map Contract
- Unique keys — each key maps to at most one value.
- Keys and values can be any object (including
nullinHashMap). Mapdoes not extendCollection— it’s a separate hierarchy.
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.put("Alice", 31); // overwrites previous value
System.out.println(ages); // {Alice=31, Bob=25}
Map Implementations
| Implementation | Order | Null Key | Null Values | Get/Put | Backed By |
|---|---|---|---|---|---|
HashMap |
None | ✅ (one) | ✅ | O(1) | Hash table |
LinkedHashMap |
Insertion order | ✅ (one) | ✅ | O(1) | Hash table + linked list |
TreeMap |
Sorted by key | ❌ | ✅ | O(log n) | Red-black tree |
Hashtable |
None | ❌ | ❌ | O(1) | Legacy, synchronized |
ConcurrentHashMap |
None | ❌ | ❌ | O(1) | Thread-safe |
Essential Methods
Adding and Retrieving
Map<String, String> capitals = new HashMap<>();
capitals.put("France", "Paris");
capitals.put("Japan", "Tokyo");
capitals.put("India", "New Delhi");
capitals.get("France"); // "Paris"
capitals.get("Germany"); // null (not found)
capitals.getOrDefault("Germany", "Unknown"); // "Unknown"
Checking and Removing
capitals.containsKey("Japan"); // true
capitals.containsValue("Tokyo"); // true
capitals.remove("Japan"); // removes and returns "Tokyo"
capitals.remove("France", "London"); // false — value doesn't match
capitals.size(); // 2
capitals.isEmpty(); // false
Java 8+ Map Methods
putIfAbsent()
capitals.putIfAbsent("France", "Lyon");
// Does nothing — "France" already exists
// Returns existing value "Paris"
computeIfAbsent()
Compute a value only if the key is absent — perfect for caches:
Map<String, List<String>> groups = new HashMap<>();
groups.computeIfAbsent("fruits", k -> new ArrayList<>()).add("apple");
groups.computeIfAbsent("fruits", k -> new ArrayList<>()).add("banana");
// {fruits=[apple, banana]}
compute() and computeIfPresent()
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"hello", "world", "hello", "java"};
for (String word : words) {
wordCount.compute(word, (k, v) -> v == null ? 1 : v + 1);
}
// {hello=2, world=1, java=1}
// Or more elegantly:
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
merge()
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 80);
scores.merge("Alice", 10, Integer::sum); // 80 + 10 = 90
scores.merge("Bob", 75, Integer::sum); // new entry: 75
replaceAll()
Map<String, Integer> prices = new HashMap<>(Map.of("A", 100, "B", 200));
prices.replaceAll((key, value) -> value + 10);
// {A=110, B=210}
Iterating Over a Map
1. entrySet() — Key + Value (Most Common)
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
2. keySet() — Keys Only
for (String name : ages.keySet()) {
System.out.println(name);
}
3. values() — Values Only
for (Integer age : ages.values()) {
System.out.println(age);
}
4. forEach() (Java 8+)
ages.forEach((name, age) -> System.out.println(name + ": " + age));
Immutable Maps (Java 9+)
// Up to 10 entries
Map<String, Integer> map = Map.of(
"A", 1,
"B", 2,
"C", 3
);
// Any number of entries
Map<String, Integer> map2 = Map.ofEntries(
Map.entry("A", 1),
Map.entry("B", 2),
Map.entry("C", 3)
);
// Copy of mutable map (Java 10+)
Map<String, Integer> copy = Map.copyOf(mutableMap);
Map.of()does not allow null keys or values, and throwsIllegalArgumentExceptionon duplicate keys.
Practical Example: Word Frequency Counter
public Map<String, Long> countWords(String text) {
return Arrays.stream(text.toLowerCase().split("\\W+"))
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()));
}
// Without streams:
public Map<String, Integer> countWords(String text) {
Map<String, Integer> freq = new HashMap<>();
for (String word : text.toLowerCase().split("\\W+")) {
freq.merge(word, 1, Integer::sum);
}
return freq;
}
Choosing the Right Map
Need fastest get/put?
└── HashMap (O(1), no order)
Need insertion order?
└── LinkedHashMap (O(1), ordered)
Need sorted keys?
└── TreeMap (O(log n), sorted)
Need thread safety?
└── ConcurrentHashMap
Need enum keys?
└── EnumMap (most efficient)
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Not overriding hashCode() for custom keys |
Keys “disappear” from map | Override equals() + hashCode() |
| Mutating a key after putting it | Hash changes, entry is lost | Use immutable keys |
Iterating keySet() then calling get() |
Double lookup (wasteful) | Use entrySet() |
Using Hashtable |
Legacy, slow | Use HashMap or ConcurrentHashMap |
Key Takeaways
Map= unique keys mapping to values. Not part ofCollection.HashMapis the default — O(1), no ordering guarantee.- Use
merge(),computeIfAbsent(), andgetOrDefault()for clean code. - Iterate with
entrySet()for best performance. - Use
Map.of()for small immutable maps. - Always override
equals()+hashCode()for custom key types.
What’s Next?
Next: HashMap Deep Dive — hash table internals, buckets, collisions, load factor, and the treeification threshold.