W3docs

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:

  • put returns the previous value for that key, or null if there was no mapping. That's how you implement "insert if absent" idioms — except you don't need to, because putIfAbsent does exactly that and is clearer.

  • get returning null means either "the key isn't there" or "the key is there but its value is null." That's an ambiguity if your map allows null values; use containsKey to disambiguate, or — better — use getOrDefault to 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) but def instead of null.
  • putIfAbsent(k, v)put only 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:

Classnull keynull value
HashMapone allowedmany allowed
LinkedHashMapone allowedmany allowed
TreeMapnomany allowed
Hashtablenono
ConcurrentHashMapnono
Map.of(...) (immutable)nono

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

ClassBacking structureIteration orderUse
HashMaphash tableunspecifiedthe default
LinkedHashMaphash table + linked listinsertion or access orderLRU caches, predictable iteration
TreeMapred-black treesorted by keyrange queries on keys, sorted output
Hashtablehash table, synchronizedunspecifiedlegacy; rarely the right choice
ConcurrentHashMapstriped hash tableunspecifiedmulti-threaded code
EnumMapbit-array-indexedenum orderMap<MyEnum, V>
Map.of(...)immutableunspecifiedsmall 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.

java— editable, runs on the server

What to take from the run:

  • merge(word, 1, Integer::sum) is the modern, idiomatic word-count. No get/put/null-check anywhere.
  • computeIfAbsent creates the empty list exactly once per key — a clean way to build a Map<K, List<V>> without sprinkling if (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.
  • getOrDefault removes the most common reason to check for null. Use it whenever there's a sensible default.
  • A HashMap and a TreeMap with the same entries are equals to 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.

Practice

Practice
`counts` is a `HashMap<String, Integer>`. Which line is the idiomatic way to increment the count for `'java'`, treating an absent key as starting from zero?
`counts` is a `HashMap<String, Integer>`. Which line is the idiomatic way to increment the count for `'java'`, treating an absent key as starting from zero?
Was this page helpful?