Java Map Interface

In the last lesson you learned about Java TreeSet, which keeps unique items in sorted order. But so much real data comes in pairs: a username and a password, a product and its price. To store pairs like that, Java gives you the Map.

🤔 Why a Map?

Imagine you are storing phone numbers for your friends. With a list, you would store the names in one place and the numbers in another, and hope the positions line up. To find Alex’s number you would loop through every name, count the position, then read the same position in the number list. That is slow and easy to get wrong.

A Map fixes this. You store the name and the number together as one pair. Then you just ask “what is the number for Alex?” and the Map hands it back at once. No looping, no counting positions.

The pain a Map removes:

  • Two parallel lists mean you keep both in sync by hand.
  • Looking up a value means scanning the whole list, item by item.
  • One wrong index reads the wrong person’s data.
  • A Map ties each value to a key, so you look up by key, not by position. Fast and clear.

🧩 What is a Map?

A Map stores data as key-value pairs. Think of a real dictionary. The word is the key, and its meaning is the value.

The rules that matter:

  • A key is the label you search by. A value is the data you store under it.
  • Keys are unique. Putting a value under a key that already exists replaces the old value.
  • Values can repeat. Two different keys can point to the same value.
  • A Map is not a Collection. The Collections family (List, Set, Queue) stores single items. A Map stores pairs, so it sits on its own.

🛠️ Building a Map: put and get

Let’s build a small phone book. We use put to add a pair and get to read a value back by its key. We program to the Map type on the left and pick a HashMap on the right.

import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("Alex", "555-0101");
phoneBook.put("Riya", "555-0199");
phoneBook.put("Sam", "555-0123");
System.out.println("Riya's number: " + phoneBook.get("Riya"));
System.out.println("Whole book: " + phoneBook);
}
}

Reading the code from the top:

  • The keys are Strings (names) and the values are Strings (numbers).
  • Each put stores one name-to-number pair.
  • get("Riya") finds the pair with key "Riya" and returns just its value.
  • We never looped. We asked by key and got the answer straight away.

Output

Riya's number: 555-0199
Whole book: {Alex=555-0101, Riya=555-0199, Sam=555-0123}

Keys are unique, so put replaces

If you call put("Alex", "555-9999") after Alex is already in the map, the old number is thrown away and replaced. There is never a second “Alex” key. Use this on purpose when you want to update a value.

🔑 The core Map methods

These are the methods you will reach for again and again.

  • put(key, value) adds or replaces a pair.
  • get(key) returns the value for a key, or null if the key is missing.
  • getOrDefault(key, fallback) returns the value, or your fallback when the key is missing.
  • containsKey(key) returns true if the key exists.
  • containsValue(value) returns true if any key holds that value.
  • remove(key) deletes the pair for that key.
  • size() returns how many pairs are stored.
  • putIfAbsent(key, value) adds the pair only if the key is not already there.
  • keySet() returns all the keys as a Set.
  • values() returns all the values as a Collection.
  • entrySet() returns all the pairs, so you can read both key and value.

Let’s see the everyday ones in action.

import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("apple", 12);
stock.put("banana", 7);
System.out.println("Apples: " + stock.get("apple"));
System.out.println("Has banana key? " + stock.containsKey("banana"));
System.out.println("Anyone with 7? " + stock.containsValue(7));
stock.remove("banana");
System.out.println("After remove: " + stock);
System.out.println("Size: " + stock.size());
}
}

Walking through the calls:

  • get("apple") returns the count stored under that key.
  • containsKey("banana") checks for the key and returns true.
  • containsValue(7) scans the values and finds one, so it returns true.
  • remove("banana") deletes that whole pair, then size() reports what is left.

Output

Apples: 12
Has banana key? true
Anyone with 7? true
After remove: {apple=12}
Size: 1

🛟 getOrDefault: a safe read

A plain get on a missing key returns null, and null causes crashes. The fix:

  • getOrDefault lets you supply a fallback value for when the key is not found.
  • That means you never have to deal with null.
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> views = new HashMap<>();
views.put("home", 40);
// "about" was never added
int homeViews = views.getOrDefault("home", 0);
int aboutViews = views.getOrDefault("about", 0);
System.out.println("Home: " + homeViews);
System.out.println("About: " + aboutViews);
}
}

Here is what makes this safe:

  • getOrDefault("home", 0) finds the key, so it returns the real value, 40.
  • getOrDefault("about", 0) misses the key, so it returns the fallback, 0.
  • The result is always a real number, never null.
  • This is perfect for counting: read the count with a fallback of 0, add one, put it back.

Output

Home: 40
About: 0

🔁 Iterating with entrySet

To loop over a Map you usually want both the key and the value of every pair. The cleanest way is entrySet():

  • It returns each pair as an Entry object.
  • You read its parts with getKey() and getValue().
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> prices = new HashMap<>();
prices.put("pen", 2);
prices.put("notebook", 5);
prices.put("eraser", 1);
for (Map.Entry<String, Integer> entry : prices.entrySet()) {
String item = entry.getKey();
int price = entry.getValue();
System.out.println(item + " costs " + price);
}
}
}

Reading the loop:

  • prices.entrySet() gives us every pair as an Entry.
  • Each time around, entry.getKey() is the item name and entry.getValue() is its price.
  • We use both together, with no second lookup.
  • Need only keys? Loop keySet(). Only values? Loop values(). Both? entrySet(), and it is the fastest.

Output

pen costs 2
notebook costs 5
eraser costs 1

HashMap order is not guaranteed

The lines above might print in a different order on your machine. A plain HashMap does not promise any order. If the order matters to you, read the implementations section below to pick LinkedHashMap or TreeMap instead.

🧮 Smarter updates: putIfAbsent, merge, compute

These methods fold an “if the key exists, do this, else do that” block into one call.

  • putIfAbsent(key, value) sets the value only if the key is not there yet. An existing value is left alone.
  • merge(key, value, rule) combines a new value with the existing one using a rule you give. A missing key just stores the new value.
  • compute(key, rule) builds a fresh value from the key and its current value.

The merge method is a clean way to count things. Let’s count how many times each word appears.

import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String[] words = { "red", "blue", "red", "red", "blue" };
Map<String, Integer> counts = new HashMap<>();
for (String word : words) {
// start at 1, or add 1 to the existing count
counts.merge(word, 1, Integer::sum);
}
System.out.println(counts);
}
}

Here is the trick that makes merge shine:

  • First time a word is seen, the key is missing, so merge stores the starting value, 1.
  • Every later time, merge adds the old count and the new 1 with Integer::sum.
  • One line replaces a whole “check if present, then branch” block.

Output

{red=3, blue=2}

🗂️ The three main implementations

Map is an interface, so you always create one of its real classes. There are three, and each keeps its keys in a different order.

  • HashMap keeps no order. Fastest for put and get, both near O(1). Use it when order does not matter, which is most of the time.
  • LinkedHashMap keeps keys in insertion order, the order you added them. Almost as fast as HashMap. Use it for predictable iteration.
  • TreeMap keeps keys in sorted order automatically. Its put and get are O(log n), a little slower. Use it when you need sorted keys.

The same pairs go into each one. Only the order they come back in changes.

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> hash = new HashMap<>();
Map<String, Integer> linked = new LinkedHashMap<>();
Map<String, Integer> tree = new TreeMap<>();
for (Map<String, Integer> m : new Map[]{ hash, linked, tree }) {
m.put("banana", 1);
m.put("apple", 2);
m.put("cherry", 3);
}
System.out.println("HashMap: " + hash);
System.out.println("LinkedHashMap: " + linked);
System.out.println("TreeMap: " + tree);
}
}

Reading the output tells the whole story:

  • The HashMap shows keys in some internal order, not the order we added them.
  • The LinkedHashMap shows the order we put them in: banana, apple, cherry.
  • The TreeMap shows them sorted: apple, banana, cherry.

Output

HashMap: {banana=1, cherry=3, apple=2}
LinkedHashMap: {banana=1, apple=2, cherry=3}
TreeMap: {apple=2, banana=1, cherry=3}

Each has its own lesson coming up. For now: HashMap for speed, LinkedHashMap for insertion order, TreeMap for sorted keys.

🎯 Program to the Map interface

Every example above wrote the interface on the left and the class on the right. This habit is called programming to the interface.

  • The left side is the interface, Map. The right side is the class, like HashMap.
  • Need sorted keys later? Change only the right side to new TreeMap<>().
  • The rest of your code keeps working, because it only used methods the Map interface promises.
  • Make parameters and return types Map too, so callers can pass any kind of map.

⚠️ Common Mistakes

A few Map slip-ups bite beginners over and over. The wrong way and the right way for each.

NullPointerException from auto-unboxing a missing key

When you store numbers, get returns an Integer, and a missing key returns null. Assigning that null to a plain int makes Java unwrap null, which crashes.

Map<String, Integer> counts = new HashMap<>();
// ❌ Wrong: "apple" is missing, so get returns null, and int cannot hold null
int n = counts.get("apple"); // throws NullPointerException

Use getOrDefault so a missing key gives a real number instead of null.

Map<String, Integer> counts = new HashMap<>();
// ✅ Right: missing key falls back to 0
int n = counts.getOrDefault("apple", 0);

A custom key class with no equals and hashCode

A HashMap finds keys using hashCode() and equals(). If your key is your own class and you skip these, two objects with the same data count as different keys, so lookups fail.

// ❌ Wrong: Point has no equals/hashCode, so the lookup misses
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Map<Point, String> map = new HashMap<>();
map.put(new Point(1, 2), "start");
System.out.println(map.get(new Point(1, 2))); // prints null, not "start"

Define both equals and hashCode (a Java record does this for you automatically), and the lookup works.

// ✅ Right: a record generates equals and hashCode for you
record Point(int x, int y) {}
Map<Point, String> map = new HashMap<>();
map.put(new Point(1, 2), "start");
System.out.println(map.get(new Point(1, 2))); // prints "start"

Treating a Map like a Collection

A Map is not a Collection, so it has no add method and you cannot loop over it directly with a for-each.

Map<String, Integer> map = new HashMap<>();
// ❌ Wrong: there is no add() on a Map, and you cannot for-each a Map itself
// map.add("apple", 1);
// for (var x : map) { ... }

Use put to add, and loop over entrySet(), keySet(), or values() to iterate.

Map<String, Integer> map = new HashMap<>();
// ✅ Right
map.put("apple", 1);
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}

✅ Best Practices

A few habits keep your Map code clean and safe:

  • Program to the Map interface. Declare variables, parameters, and return types as Map, and pick the class only at the new call.
  • Use getOrDefault for reads that might miss. It removes a whole class of null crashes, especially when counting.
  • Loop with entrySet() when you need both key and value. It is clearer and faster than looking the value up again inside a keySet loop.
  • Give custom key classes equals and hashCode. Use a record for the easiest correct version.
  • Pick the implementation for the order you need. HashMap for speed, LinkedHashMap for insertion order, TreeMap for sorted keys.
  • Reach for merge or compute for update logic. They fold the “check then update” branch into one readable line.

🧩 What You’ve Learned

Nicely done. Let’s recap the Map interface.

  • ✅ A Map stores key-value pairs, and it is not a Collection.
  • Keys are unique (a new put replaces the old value) and values can repeat.
  • ✅ Core methods: put, get, getOrDefault, containsKey, containsValue, remove, size, keySet, values, entrySet, putIfAbsent, merge, compute.
  • ✅ Loop with entrySet() and read each pair with getKey() and getValue().
  • ✅ The three main classes are HashMap (no order), LinkedHashMap (insertion order), and TreeMap (sorted keys).
  • Program to the Map interface so you can swap the implementation freely.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does a Map store?

    Why: A Map stores pairs: each value is kept under a unique key.

  2. 2

    What happens when you put a value under a key that already exists?

    Why: Keys are unique, so a new put under the same key replaces the old value.

  3. 3

    Why use getOrDefault instead of get?

    Why: getOrDefault returns your fallback for a missing key, avoiding null.

  4. 4

    Which implementation keeps the keys in sorted order?

    Why: TreeMap keeps its keys sorted automatically; HashMap has no order and LinkedHashMap keeps insertion order.

🚀 What’s Next?

You now know the Map interface and the methods every map shares. Time to go deep on the one you will use most. The HashMap is the fast, no-order workhorse for key-value data. Let’s master it.

Java HashMap

Share & Connect