Java hashCode() Method
Table of Contents + β
In the last lesson you learned about the Java equals() method. But the moment you override equals(), you have a hidden job to do.
- Skip it, and your objects start vanishing inside collections.
- You add one to a
HashSet, ask if it is there, and Java says no. - The fix is the hashCode() method, which always travels with
equals().
Letβs learn why, and how to write it correctly.
π€ What is hashCode()?
hashCode() returns an int. Every object has one, inherited from Object. That number is called a hash code.
- It is a quick label that hash-based collections use to sort objects into buckets.
HashMapandHashSetdo not search every item one by one. That is slow.- They group objects into buckets, and the hash code picks the bucket.
Here is the smallest example of calling it.
public class Main { public static void main(String[] args) { String name = "Alex"; System.out.println(name.hashCode()); }}Output
2043177Stringalready has a goodhashCode(), so it hands back a number.- Different strings give different numbers.
- The same string always gives the same number. The same input, the same hash code, every time.
πͺ£ Why hash-based collections need it
Picture a row of buckets, each with a number on it. Here is the order a set follows when it searches:
// Rough idea of what HashSet.contains does inside// 1. Ask the object for its hashCode() -> pick a bucket// 2. Go to that one bucket only// 3. Inside the bucket, use equals() to find the exact matchhashCode()narrows the search to one bucket. That is what makes it fast.equals()confirms the exact item inside that bucket.- The set never looks in the other buckets.
- So if two equal objects land in different buckets, the set looks in the wrong place and misses the match.
This is the root of every bug in this lesson.
π The equals-hashCode contract
The equals-hashCode contract is a promise your code has to keep.
- If two objects are equal by
equals(), they must return the samehashCode(). hashCode()on the same object must return the same number every time, unless its fields change.- Two objects with the same hash code do not have to be equal. Sharing a bucket is fine.
If a.equals(b) is true -> a.hashCode() == b.hashCode() must be trueThe rule that bites people is the first one. Break it, and collections stop working. Letβs prove it.
π₯ What breaks if you skip it
This User overrides equals() so two users match on id and email. But it forgets hashCode(), the classic mistake.
import java.util.Objects;
class User { int id; String email;
User(int id, String email) { this.id = id; this.email = email; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User other = (User) o; return id == other.id && Objects.equals(email, other.email); } // β No hashCode() here. Big problem.}Now put one user in a HashSet and ask if an equal user is inside.
import java.util.HashSet;
public class Main { public static void main(String[] args) { HashSet<User> users = new HashSet<>(); users.add(new User(1, "alex@mail.com"));
User same = new User(1, "alex@mail.com"); System.out.println("Are they equal? " + users.iterator().next().equals(same)); System.out.println("Does the set contain it? " + users.contains(same)); }}Output
Are they equal? trueDoes the set contain it? falseThe two users are equal, but the set says it does not contain the second one. How?
- The first user used the default
hashCode(), which gave some number. - The second user is a new object, so the default gave a different number.
- Different numbers mean different buckets.
- The set went to the empty bucket, found nothing, and reported βnot hereβ. It never reached
equals().
You overrode equals() but left hashCode() alone, so the two methods disagree.
π οΈ Writing a good hashCode()
The fix is easy. Override hashCode() and build the number from the same fields equals() uses. The cleanest way is Objects.hash(...), which combines fields into one int:
import java.util.Objects;
class User { int id; String email;
User(int id, String email) { this.id = id; this.email = email; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User other = (User) o; return id == other.id && Objects.equals(email, other.email); }
@Override public int hashCode() { return Objects.hash(id, email); // β
same fields as equals }}The key detail is which fields go into Objects.hash(...).
- They are
idandemail, the exact fieldsequals()compares. - That keeps the two methods consistent.
- Equal users share
idandemail, so they get the same hash code. Promise kept.
Run the same test with the fixed class:
import java.util.HashSet;
public class Main { public static void main(String[] args) { HashSet<User> users = new HashSet<>(); users.add(new User(1, "alex@mail.com"));
User same = new User(1, "alex@mail.com"); System.out.println("hashCode A: " + users.iterator().next().hashCode()); System.out.println("hashCode B: " + same.hashCode()); System.out.println("Does the set contain it? " + users.contains(same)); }}Output
hashCode A: 1843517506hashCode B: 1843517506Does the set contain it? trueBoth users return the same hash code, so they land in the same bucket. The set runs equals(), finds the match, and says yes.
π Consistency with the fields in equals
The golden rule: use the same fields in both methods.
- The fields that decide equality must be the fields that build the hash code. Not more, not fewer.
- If
hashCode()uses a fieldequals()ignores, you break the contract. - Then two objects could be equal yet hash differently, and the vanishing-object bug returns.
// equals compares: id, email// hashCode must use: id, email β
matching// hashCode using: id, email, lastLogin β extra field breaks the contract𧬠The default Object.hashCode()
Before you override it, the default hashCode() is identity-based.
- It is built from the object itself, roughly its place in memory, not its fields.
- So two separate objects almost always get different numbers, even with identical fields.
This snippet shows the default on a class that overrides nothing:
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; }}
public class Main { public static void main(String[] args) { Point a = new Point(2, 3); Point b = new Point(2, 3); System.out.println(a.hashCode()); System.out.println(b.hashCode()); System.out.println(a.hashCode() == b.hashCode()); }}Output
15545471251846274136falseTwo points with the same x and y, but different hash codes. The default ignores fields. That is why a field-based equals() needs a field-based hashCode() to match it.
Same value every run?
Identity-based hash codes change from run to run, so the exact numbers in your output may differ from the ones shown here. What stays true is the comparison result. Equal objects with a proper hashCode() always match, and the default one usually will not.
β οΈ Common Mistakes
These three trip up almost everyone.
Overriding equals() but not hashCode(). This is the headline bug of the whole lesson. Your objects disappear inside HashSet and HashMap.
// β equals overridden, hashCode left as the default@Overridepublic boolean equals(Object o) { /* compares fields */ }// no hashCode -> contract broken, lookups fail// β
always override both together@Overridepublic boolean equals(Object o) { /* compares id, email */ }
@Overridepublic int hashCode() { return Objects.hash(id, email); }Using mutable fields in hashCode(). If a field can change after the object is stored in a collection, do not build the hash code from it. The object would move buckets while the collection still looks in the old one.
// β email can change later; the object gets lost in the setset.add(user);user.email = "new@mail.com"; // hashCode just changedset.contains(user); // now false, even though it is the same object// β
base hashCode on a stable field like a database id that never changes@Overridepublic int hashCode() { return Objects.hash(id); } // id is fixed for lifeReturning a constant. Some people βfixβ the contract by returning the same number for every object. It is technically legal, because equal objects do return equal hash codes. But it is terrible.
// β every object lands in the same bucket@Overridepublic int hashCode() { return 1; }// β
spread objects across buckets using their fields@Overridepublic int hashCode() { return Objects.hash(id, email); }- When everything returns
1, every object piles into one bucket. - The collection then checks them one by one with
equals(), the slow search you wanted to avoid. - A good hash code spreads objects out so each bucket stays small.
β Best Practices
- β
Override both or neither. The day you write
equals(), writehashCode()in the same sitting. They are a pair. - β Use the same fields in both. The fields that decide equality must be the fields that build the hash code. Nothing extra.
- β
Reach for
Objects.hash(field1, field2). It combines fields into a solid hash code in one line, so you do not write the math by hand. - β Prefer stable fields. Build the hash code from values that do not change after the object is stored, like an id, not from values a user can edit.
- β
Let your IDE help. Most editors generate matching
equals()andhashCode()from the fields you pick. Use that and the pair stays in sync. - β Never return a constant. It keeps the contract but destroys performance. Spread objects across buckets.
π§© What Youβve Learned
Good work. That was a lot, so letβs recap the hashCode() method.
- β
hashCode()returns anintthat hash-based collections use to pick a bucket for an object. - β
HashMapandHashSetfind items fast by jumping to one bucket, then usingequals()inside it. - β The contract: equal objects must return equal hash codes, and the hash code must stay steady for an unchanged object.
- β
If you override
equals()but nothashCode(), objects go missing in sets and maps, even when an equal one was added. - β
Write a good one with
Objects.hash(...)using the same fields asequals(). - β
The default
Object.hashCode()is identity-based, so it ignores your fields. - β Avoid mutable fields and never return a constant.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does the equals-hashCode contract require?
Why: The core rule is that if two objects are equal, they must return the same hashCode. The reverse is not required.
- 2
You override equals() but not hashCode() and add an object to a HashSet. What happens when you check for an equal object?
Why: The default hashCode gives different numbers for different objects, so the set looks in the wrong bucket and the equals step never runs.
- 3
Which is the cleanest way to build a hashCode from two fields?
Why: Objects.hash(id, email) combines the same fields used in equals into one solid int.
- 4
What is the default Object.hashCode() based on?
Why: The default hashCode is identity-based, so two objects with identical fields usually get different numbers.
π Whatβs Next?
You now control how your objects behave inside Javaβs collections. Next we step back from the Object class and look at how Java decides who is allowed to see and use your classes, fields, and methods. It is all about keeping the right doors open and the wrong ones closed.