equals() and hashCode() in Java: Comparing Objects Correctly
Learn how equals() and hashCode() really work in Java, why you must override them together, and how they affect HashMap and HashSet — with clear beginner examples.
Back in the Strings lesson you learned to compare text with .equals() instead of ==. This lesson goes one level deeper: what does equals() actually do, how do you make it work for your own objects, and why does its silent partner hashCode() matter so much? Get this right and your objects behave correctly inside collections; get it wrong and you'll hit bugs that are genuinely baffling. Let's make it clear.
== vs equals(), one more time
==checks if two references point to the exact same object in memory (identity)..equals()checks if two objects are meaningfully equal (same content).
The catch: by default, a class you write inherits equals() from Object, and that default version just does ==. So unless you say otherwise, two "identical" objects are considered not equal:
class Point { int x, y; Point(int x,int y){this.x=x; this.y=y;} } Point a = new Point(1, 2); Point b = new Point(1, 2); System.out.println(a == b); // false — different objects System.out.println(a.equals(b)); // false too! (default = ==) — probably NOT what you want
Overriding equals()
To make two points with the same coordinates count as equal, we override equals() to compare the actual fields:
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Point)) return false; Point p = (Point) o; return x == p.x && y == p.y; // equal if fields match }
Now a.equals(b) returns true — the behaviour you'd expect.
The golden rule: always override hashCode() too
equals(), you MUST also override hashCode(). This is one of the most important rules in Java, and skipping it causes bugs that are very hard to track down. The contract is simple: two objects that are equal must return the same hashCode.Why? Because HashMap and HashSet rely on it
Collections like HashMap and HashSet use hashCode() to decide where to store an object (which "bucket"), and only then use equals() to confirm a match. If two equal objects have different hash codes, they get filed in different buckets — so the collection thinks they're different, and lookups mysteriously fail:
Set<Point> set = new HashSet<>(); set.add(new Point(1, 2)); System.out.println(set.contains(new Point(1, 2))); // WITHOUT hashCode(): false (!) — the bug // WITH matching hashCode(): true — correct
Writing hashCode() (the easy way)
You almost never write the hashing maths by hand — Java gives you a helper. Just pass it the same fields you used in equals():
import java.util.Objects; @Override public int hashCode() { return Objects.hash(x, y); // same fields as equals() }
That's it. Use the same fields in both methods and the contract is satisfied.
The good news: your IDE (and Lombok) do this for you
In practice you rarely type these by hand. IntelliJ and Eclipse can generate correct equals() and hashCode() in two clicks, and libraries like Lombok add them with a single annotation. But — and this matters — you should understand what they generate and why, so you can spot the bug when it's missing.
This is one of those topics that seems academic until it bites you. I once spent an afternoon on a HashSet that kept adding "duplicate" objects — the cause was an equals() without a matching hashCode(). Ever since, I treat them as an inseparable pair: override one, override the other, using the same fields. Remember that single rule and you'll sidestep a bug that trips up even experienced developers.
Key takeaways
- Default
equals()just does==; override it to compare your object's fields. - If you override
equals(), always overridehashCode()— using the same fields. - Equal objects must share the same hashCode, or
HashMap/HashSetbreak. - Use
Objects.hash(...), or let your IDE/Lombok generate both — but understand why.
← Previous: Lesson 24 — Enums & Wrapper Classes
Next: Lesson 26 — Immutability in Java →