Java Map Interface
Key-value mappings in Java with the Map interface — put, get, remove, keySet, values, entrySet.
This chapter covers the Map contract: its seven core methods, the three views it exposes for iteration, the Java 8 default methods that make modern map code concise, null-handling rules per implementation, and how maps compare for equality. By the end you'll know which idioms to reach for and which standard implementation fits a given job.
Map<K, V> is the other half of the collections framework. Unlike the Collection interface, it doesn't extend Collection — it's a separate hierarchy, because storing keys associated with values is a different abstraction from storing a bag of elements. Internally most Set implementations are just Maps where you ignore the value, so Map is in some sense the primary structure and Set is the simpler sibling.
The contract is short: every key maps to at most one value, the keys form a set (no duplicate keys), and the values are an arbitrary collection (duplicate values are fine). What changes between implementations is iteration order, null-handling, ordering invariants, and thread safety — but the seven core methods below behave the same on all of them.
The seven core methods
V put(K key, V value); // insert or overwrite; returns previous value or null
V get(Object key); // lookup; returns null if missing
V remove(Object key); // delete; returns previous value or null
boolean containsKey(Object k); // does the key exist (even if value is null)?
boolean containsValue(Object v); // O(n) scan of values
int size();
boolean isEmpty();A few subtleties worth internalising:
-
putreturns the previous value for that key, ornullif there was no mapping. That's how you implement "insert if absent" idioms — except you don't need to, becauseputIfAbsentdoes exactly that and is clearer. -
getreturningnullmeans either "the key isn't there" or "the key is there but its value isnull." That's an ambiguity if your map allows null values; usecontainsKeyto disambiguate, or — better — usegetOrDefaultto provide a sentinel:int count = counts.getOrDefault("java", 0); // 0 if absent
The three views
A Map isn't iterable directly. To iterate, you ask it for one of three views of its contents:
Set<K> keys = map.keySet();
Collection<V> values = map.values();
Set<Map.Entry<K, V>> es = map.entrySet();These views are live — they reflect changes to the underlying map, and changes made through the view propagate back. Removing an entry through entrySet() removes it from the map; iterating keySet() and calling iterator.remove() removes the entry. You can't add to keySet or values (no value or key to pair with), but you can clear or remove.
Iteration almost always uses entrySet() — getting both pieces of each pair at once is cheaper than calling get(k) for every key:
for (Map.Entry<String, Integer> e : counts.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}Or, the lambda form added in Java 8:
counts.forEach((k, v) -> System.out.println(k + " -> " + v));The Java 8 default methods that actually matter
Java 8 added several Map methods that take a function and behave atomically. They turn a lot of three-line patterns into one-liners:
getOrDefault(k, def)—get(k)butdefinstead ofnull.putIfAbsent(k, v)—putonly if the key is missing.computeIfAbsent(k, fn)— atomically compute the value if absent, store it, return it. The cornerstone of "memoize this expensive call":Map<String, List<Order>> byUser = new HashMap<>(); byUser.computeIfAbsent(order.user(), u -> new ArrayList<>()).add(order);computeIfPresent(k, biFn)— recompute only if the key already exists. Useful for counters that should ignore unseen keys.compute(k, biFn)— universal: pass the current value (or null), get the new one. Removes the entry if the function returns null.merge(k, v, biFn)— combine a new value with the existing one if any. The textbook counter:for (String w : words) { counts.merge(w, 1, Integer::sum); // first time: stores 1; subsequent: adds }
These are the operations that make modern Java map-handling concise. Use them instead of get/put pairs.
Null keys and null values
The rules depend on the implementation:
| Class | null key | null value |
|---|---|---|
HashMap | one allowed | many allowed |
LinkedHashMap | one allowed | many allowed |
TreeMap | no | many allowed |
Hashtable | no | no |
ConcurrentHashMap | no | no |
Map.of(...) (immutable) | no | no |
The general rule for new code: don't store nulls in a map. Use Optional, a sentinel value, or just don't put the entry. The factory Map.of enforces this for you.
Equality across implementations
Two maps are equals if their entrySet()s are equal — same keys, same values, regardless of iteration order or implementation. A HashMap and a TreeMap of the same key-value pairs compare equal. That's the same "structural equality" rule Set follows.
The standard implementations, at a glance
| Class | Backing structure | Iteration order | Use |
|---|---|---|---|
HashMap | hash table | unspecified | the default |
LinkedHashMap | hash table + linked list | insertion or access order | LRU caches, predictable iteration |
TreeMap | red-black tree | sorted by key | range queries on keys, sorted output |
Hashtable | hash table, synchronized | unspecified | legacy; rarely the right choice |
ConcurrentHashMap | striped hash table | unspecified | multi-threaded code |
EnumMap | bit-array-indexed | enum order | Map<MyEnum, V> |
Map.of(...) | immutable | unspecified | small fixed maps |
The next chapters cover the everyday choices in depth: HashMap, LinkedHashMap, and TreeMap. ConcurrentHashMap and EnumMap come up in later parts.
A worked example: counters, grouping, and the three views
The program below shows the modern map idioms — merge for counting, computeIfAbsent for grouping, all three views, and the getOrDefault versus get distinction.
What to take from the run:
merge(word, 1, Integer::sum)is the modern, idiomatic word-count. Noget/put/null-check anywhere.computeIfAbsentcreates the empty list exactly once per key — a clean way to build aMap<K, List<V>>without sprinklingif (m.get(k) == null) m.put(k, new ArrayList<>())everywhere.- The three views are live windows into the same map;
entrySet()is the cheapest way to iterate when you need both halves of each pair. getOrDefaultremoves the most common reason to check for null. Use it whenever there's a sensible default.- A
HashMapand aTreeMapwith the same entries areequalsto each other; the only thing that changes is the iteration order.
What's next
The default implementation — and the one you'll see in 90% of Java code — is hash-table-backed. HashMap is the next chapter; we'll cover the bucket array, the Java 8 treeification optimisation, and what to do when your keys are your own classes.