Java HashMap

In the last lesson you learned about the Java Map interface. When data comes in pairs, like a username and a password, you want to look up the value by its key, fast. For paired data like this, Java has the HashMap, one of the most useful collections you will ever learn.

πŸ€” Why do we need a Map?

Say you store user ages. With two lists, one of names and one of ages lined up by position, you hit real pain:

  • Finding β€œRiya” means walking the names one by one. A million names is a million checks.
  • Then you read the other list at the same position. If the two lists slip out of line, you read the wrong age.
  • The link between a name and an age only lives in your head.

A HashMap fixes all of this. You link a key (the name) straight to its value (the age). So map.get("Riya") instantly gives you 30. No searching, no second list.

🧩 What is a HashMap?

A HashMap stores data as key-value pairs. You use the key to find the value, like a dictionary: look up a word (the key), read its meaning (the value). The rules to remember:

  • Keys are unique. Putting an existing key replaces the old value, it does not add a second pair.
  • Values can repeat. Two people can both be age 25.
  • No guaranteed order. Like HashSet, the order in is not the order out.
  • One null key is allowed, and null values as many times as you like.

You declare a HashMap with two types: the key type first, then the value type, like HashMap<String, Integer>.

βš™οΈ How does it look things up so fast?

The trick is the hash code, a number from each key’s hashCode() method. Picture a row of numbered drawers:

  • put computes the key’s hash code, picks a drawer (a bucket), and drops the pair in.
  • get computes the same hash code, goes straight to that same drawer, and finds the pair.
  • So the map jumps to one drawer instead of opening every drawer.

That makes lookups O(1) on average. O(1) means the time stays about the same whether the map holds ten pairs or ten million. Keep this drawer picture in mind. It explains the custom-key rule near the end.

πŸ’‘ Putting and getting values

You add a pair with put(key, value) and read a value with get(key). Let’s store and look up some ages.

import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25); // βœ… key "Alex" maps to 25
ages.put("Riya", 30);
ages.put("Arjun", 28);
System.out.println("Riya's age: " + ages.get("Riya"));
System.out.println("Size: " + ages.size());
}
}

Reading that top to bottom:

  • HashMap<String, Integer> means String keys and Integer values.
  • Each put links a name to an age.
  • get("Riya") returns 30 instantly, no searching.
  • size() is the number of pairs.

That is the core loop: put pairs in, get values out by key.

Output

Riya's age: 30
Size: 3

✏️ Updating and the unique-key rule

Putting a value with a key that already exists replaces the old value. This is how you update a value, and it is why keys stay unique.

import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25);
ages.put("Alex", 26); // βœ… same key: replaces 25 with 26
System.out.println(ages.get("Alex"));
System.out.println("Size: " + ages.size());
}
}

We put β€œAlex” twice:

  • The second put overwrites the old value, it does not add a pair.
  • Alex’s age becomes 26, and the size stays 1.

This is the unique-key rule. To update a value, just put it again with the same key.

Output

26
Size: 1

Sometimes you want the opposite: set a value only if the key is missing. That is putIfAbsent, handy for default settings you do not want to overwrite.

import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, String> theme = new HashMap<>();
theme.put("color", "blue");
theme.putIfAbsent("color", "red"); // ❌ ignored, "color" already exists
theme.putIfAbsent("font", "Arial"); // βœ… added, "font" was missing
System.out.println(theme.get("color"));
System.out.println(theme.get("font"));
}
}

putIfAbsent("color", "red") does nothing because β€œcolor” is already set. putIfAbsent("font", "Arial") adds the pair because β€œfont” was missing. Use this when an existing value should win.

Output

blue
Arial

⚠️ Handling missing keys

A get on a missing key returns null, not an error. That null is a quiet trap. It does not crash right away, it crashes later somewhere harder to spot. So handle it on purpose.

import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25);
System.out.println(ages.get("Riya")); // null, not found
System.out.println(ages.containsKey("Riya")); // false
System.out.println(ages.containsValue(25)); // true, a value check
System.out.println(ages.getOrDefault("Riya", 0)); // βœ… 0 as a fallback
}
}

Walking through it:

  • get("Riya") returns null because Riya is missing.
  • containsKey asks β€œis this key here?” before you read.
  • containsValue asks the reverse, β€œdoes any key hold this value?” It is slower, it scans every pair.
  • getOrDefault("Riya", 0) returns 0 instead of null, which is safer.

So prefer getOrDefault whenever a sensible default exists.

Output

null
false
true
0

πŸ—‘οΈ Removing pairs and the handy methods

You remove a pair by its key with remove(key). Here are the small methods that come up all the time, in one place.

import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25);
ages.put("Riya", 30);
ages.put("Arjun", 28);
ages.remove("Arjun"); // βœ… deletes the "Arjun" pair
System.out.println(ages.keySet()); // all the keys
System.out.println(ages.values()); // all the values
System.out.println(ages.size()); // how many pairs are left
}
}

Reading that:

  • remove("Arjun") deletes that pair, dropping the map to two.
  • keySet() gives a set of just the keys.
  • values() gives a collection of just the values.
  • size() reports the count.

With put, get, getOrDefault, and containsKey, these cover almost everything day to day.

Output

[Alex, Riya]
[25, 30]
2

πŸ” Looping over a HashMap

To go through every pair, loop over the map’s entries, where each entry holds one key and its value. The cleanest way uses entrySet().

import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25);
ages.put("Riya", 30);
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " is " + entry.getValue());
}
}
}

Reading the loop:

  • entrySet() gives all the pairs, and the for-each visits each one.
  • getKey() is the name, getValue() is the age.
  • It reads the key and value in one step, so it is the fastest way.
  • You can also loop keySet() and call get(name), but that does an extra get each turn.

Whichever you use, the order is not guaranteed. Do not depend on which pair comes first.

Output

Alex is 25
Riya is 30

πŸ”’ Worked example: counting word frequency

Counting how often each thing appears is a job you will do again and again. The key is the word, the value is its running count. The clean way uses merge:

  • merge(key, value, function) stores the value if the key is new.
  • If the key exists, it combines the old and new values using the function.
  • So merge(word, 1, Integer::sum) means β€œnew word, store 1; seen word, add 1”.
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
String text = "red blue red green blue red";
HashMap<String, Integer> counts = new HashMap<>();
for (String word : text.split(" ")) {
counts.merge(word, 1, Integer::sum); // βœ… new -> 1, seen -> old + 1
}
System.out.println(counts);
}
}

That single line replaces the whole β€œget the count, add one, put it back” dance. A close cousin is compute, which changes a value based on its current value. For plain counting, merge is the clean choice.

Output

{red=3, blue=2, green=1}

πŸ”‘ Custom-class keys: override hashCode and equals

Strings and Integers already know how to hash and compare themselves. Your own class, like a Point with an x and a y, does not. Now the drawer picture matters:

  • The map uses hashCode() to pick the drawer, then equals() to confirm the key.
  • By default your class hashes by object identity, so two objects with the same data land in different drawers.
  • So get fails even though the data looks identical.

The fix is to override both hashCode() and equals() so equal data is treated as equal.

import java.util.HashMap;
import java.util.Objects;
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) { // βœ… equal data means equal key
if (!(o instanceof Point)) return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
@Override
public int hashCode() { // βœ… same data -> same hash
return Objects.hash(x, y);
}
}
public class Main {
public static void main(String[] args) {
HashMap<Point, String> labels = new HashMap<>();
labels.put(new Point(1, 2), "start");
System.out.println(labels.get(new Point(1, 2))); // a brand-new object
}
}

The get uses a different Point object, but with the same x and y:

  • hashCode() makes both objects hash to the same drawer.
  • equals() makes the map see them as the same key.
  • So the lookup succeeds. Without these, the output would be null.

The rule is short: a custom map key needs both methods, always as a pair.

Output

start

⚠️ Common Mistakes

A few HashMap slip-ups to watch for.

  • Letting a missing key turn into a crash. A missing key gives back null. If you then unbox it into a primitive int, Java throws a NullPointerException. Use a fallback instead.
// ❌ crashes if "Riya" is missing: null cannot become an int
int age = ages.get("Riya");
// βœ… safe: a default value when the key is absent
int age = ages.getOrDefault("Riya", 0);
  • Changing a key after you put it in. If you use a mutable object as a key and then change the field that its hash code is built from, the map loses the pair. It still sits in the old drawer, so a fresh lookup checks the new drawer and finds nothing.
// ❌ mutating a key after insertion: the pair becomes unreachable
Point p = new Point(1, 2);
map.put(p, "start");
p.x = 9; // the key's hash just changed under the map's feet
// βœ… treat keys as fixed; use a new key instead of editing one
map.put(new Point(9, 2), "moved");
  • A custom-class key without hashCode and equals. Two objects with the same data will not match, so get returns null. Override both methods as a pair.

  • Relying on order. A HashMap does not keep pairs in order. Use LinkedHashMap for insertion order or TreeMap for keys sorted automatically.

βœ… Best Practices

Habits for using HashMap well.

  • Reach for it on key-to-value lookups. Names to ages, IDs to records, words to counts. If your question is β€œwhat value belongs to this key?”, a map is the answer.
  • Declare both types. HashMap<String, Integer> makes the key and value types clear and lets the compiler catch mistakes.
  • Guard missing keys. Prefer getOrDefault, or check containsKey before you read, so null never surprises you.
  • Count with merge. map.merge(key, 1, Integer::sum) is the clean way to tally things.
  • Loop with entrySet(). It reads the key and value in one step, which is both clear and fast.
  • Make map keys immutable. Strings and Integers are safe by nature. For your own classes, override hashCode() and equals() and do not change a key after inserting it.

🧩 What You’ve Learned

Nicely done. Let’s recap HashMap.

  • βœ… A HashMap stores key-value pairs and looks up a value by its key, very fast (O(1) on average) thanks to hash codes and buckets.
  • βœ… Use put(key, value) to add or update, get(key) to read, and putIfAbsent to set only when missing.
  • βœ… Keys are unique (putting an existing key replaces the value); values may repeat, and the order is not guaranteed.
  • βœ… A missing key returns null; use getOrDefault or containsKey to stay safe.
  • βœ… Loop with entrySet(), count with merge, and for custom-class keys override both hashCode() and equals().

Check Your Knowledge

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

  1. 1

    What does a HashMap store?

    Why: A HashMap stores pairs of keys and values, looked up by key.

  2. 2

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

    Why: Keys are unique, so putting an existing key overwrites the old value.

  3. 3

    What does get() return for a key that is not in the map?

    Why: get returns null for a missing key, so guard with containsKey or getOrDefault.

  4. 4

    To use your own class as a HashMap key correctly, what must you override?

    Why: The map uses hashCode to pick the bucket and equals to confirm the key, so override both as a pair.

πŸš€ What’s Next?

A HashMap is fast but does not keep any order. What if you want a map that remembers the order you added pairs in? Java has the LinkedHashMap for that. Let’s learn it next.

Java LinkedHashMap

Share & Connect