Java HashMap
Table of Contents + β
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
nullvalues 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:
putcomputes the keyβs hash code, picks a drawer (a bucket), and drops the pair in.getcomputes 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
putlinks 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: 30Size: 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
putoverwrites 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
26Size: 1Sometimes 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
blueArialβ οΈ 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")returnsnullbecause Riya is missing.containsKeyasks βis this key here?β before you read.containsValueasks the reverse, βdoes any key hold this value?β It is slower, it scans every pair.getOrDefault("Riya", 0)returns 0 instead ofnull, which is safer.
So prefer getOrDefault whenever a sensible default exists.
Output
nullfalsetrue0ποΈ 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 callget(name), but that does an extrageteach turn.
Whichever you use, the order is not guaranteed. Do not depend on which pair comes first.
Output
Alex is 25Riya 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, thenequals()to confirm the key. - By default your class hashes by object identity, so two objects with the same data land in different drawers.
- So
getfails 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 primitiveint, Java throws aNullPointerException. Use a fallback instead.
// β crashes if "Riya" is missing: null cannot become an intint age = ages.get("Riya");
// β
safe: a default value when the key is absentint 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 unreachablePoint 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 onemap.put(new Point(9, 2), "moved");-
A custom-class key without
hashCodeandequals. Two objects with the same data will not match, sogetreturnsnull. Override both methods as a pair. -
Relying on order. A HashMap does not keep pairs in order. Use
LinkedHashMapfor insertion order orTreeMapfor 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 checkcontainsKeybefore you read, sonullnever 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()andequals()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, andputIfAbsentto 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; usegetOrDefaultorcontainsKeyto stay safe. - β
Loop with
entrySet(), count withmerge, and for custom-class keys override bothhashCode()andequals().
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does a HashMap store?
Why: A HashMap stores pairs of keys and values, looked up by key.
- 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
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
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.