equals(), hashCode() & Object Methods
equals() defines when two objects count as "the same value", and hashCode() must agree with it — together they are the contract every object stored in a HashSet or HashMap has to honour.
Learn equals(), hashCode() & Object Methods in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You should know classes and objects and have used the Collections (especially HashSet and HashMap ). A glance at records shows the shortcut that generates these for you.
💡 Analogy: A HashMap is a library where hashCode is the shelf number and equals is reading the title to find the exact book. To return a book you first walk to the shelf its hash points to, then check titles on that shelf. If two identical books somehow get different shelf numbers (equal by equals but different hashCode ), the librarian walks to the wrong shelf and swears the book isn't there. That is the silent bug behind "my object is in the set but contains() returns false".
The rule that keeps the library sane: equal objects must hash to the same shelf. Override one, override the other.
Every class inherits equals and hashCode from Object . By default equals is just == (reference identity) and hashCode is derived from the object's memory identity. So two objects with identical fields are not equal, and a HashSet happily stores both as duplicates.
A correct equals follows a fixed shape: identity fast-path, an instanceof check (which also rejects null ), a cast, then a field-by-field comparison. The matching hashCode uses Objects.hash(...) over the same fields . Add a readable toString while you're there.
Override equals but forget hashCode and you get the most infamous Java bug: objects that are equal but have different hash codes. They land in different buckets, so set.contains and map.get fail to find them even though an equal object is right there.
Declare the same value type as a record and the compiler generates equals , hashCode , and toString — all consistent, all derived from the components. For simple immutable data carriers this is the safest and shortest option.
Answer: illegal — it breaks the contract and breaks hash collections. The reverse (different objects sharing a hashCode) is legal; that's just a collision, which equals resolves.
Answer: because a might be null . Objects.equals(a, b) is null-safe — it returns true if both are null and never throws an NPE.
Answer: no — == compares references and new String makes a distinct object. .equals is true . Always use .equals for String value comparison.
🎯 YOUR TURN — Equal by ISBN
Override equals and hashCode on Book so two books with the same ISBN are one in a HashSet .
🧩 MINI-CHALLENGE — Record as a map key
Use a record Coord(int x, int y) as a HashMap key. Re-putting the same coordinate overwrites, and lookups by a fresh Coord must hit.
You now know what the default equals / hashCode do, the contract that binds them, how to write both correctly with instanceof , Objects.equals and Objects.hash , why a missing hashCode silently breaks hash collections, and how records hand you all of it for free.
Next up: Immutability & Defensive Copying — building objects that can't be corrupted after construction.
Practice quiz
What does the default Object.equals compare?
- The fields of the objects
- Object identity (same reference)
- The toString output
- The class names
Answer: Object identity (same reference). Without an override, equals returns true only when both references point to the very same object (==).
The equals/hashCode contract requires that...
- equal objects have equal hashCodes
- unequal objects have unequal hashCodes
- hashCode is always 0
- equals is faster than hashCode
Answer: equal objects have equal hashCodes. If a.equals(b) is true then a.hashCode() must equal b.hashCode(). The reverse is not required — collisions are allowed.
What breaks if you override equals but NOT hashCode?
- Nothing
- HashSet/HashMap lookups fail to find equal objects
- Compilation fails
- toString breaks
Answer: HashSet/HashMap lookups fail to find equal objects. Hash-based collections use hashCode to pick a bucket; if equal objects have different hashCodes they land in different buckets and can't be found.
Which helper computes a hash from several fields?
- Objects.hash(x, y)
- Objects.equals(x, y)
- Math.hash(x, y)
- Arrays.hash(x, y)
Answer: Objects.hash(x, y). Objects.hash(field1, field2, ...) returns a combined hash and handles nulls for you.
Which helper null-safely compares two fields in equals?
- a == b
- a.equals(b)
- Objects.equals(a, b)
- Objects.compare(a, b)
Answer: Objects.equals(a, b). Objects.equals(a, b) returns true if both are null or a.equals(b), avoiding a NullPointerException.
Do records generate equals and hashCode for you?
- No, you must write them
- Yes, based on all components
- Only equals
- Only hashCode
Answer: Yes, based on all components. A record automatically gets equals, hashCode and toString derived from its components — no boilerplate.
A correct equals should first check...
- this == o for a fast path
- the toString
- the hashCode
- the array length
Answer: this == o for a fast path. if (this == o) return true; is a quick identity short-circuit before the more expensive field comparison.
How should equals handle a null or wrong-type argument?
- Throw an exception
- Return true
- Return false (e.g. via instanceof)
- Ignore it
Answer: Return false (e.g. via instanceof). instanceof returns false for null and for the wrong type, so 'if (!(o instanceof T)) return false;' handles both safely.
What is a key property of equals?
- It must be symmetric: a.equals(b) == b.equals(a)
- It must be random
- It must compare hashCodes
- It must be slower than hashCode
Answer: It must be symmetric: a.equals(b) == b.equals(a). equals must be reflexive, symmetric, transitive, and consistent — symmetry being a classic one people break with inheritance.
What does Object.toString return by default?
- The field values
- ClassName@hexHashcode
- An empty string
- null
Answer: ClassName@hexHashcode. The default is getClass().getName() + '@' + Integer.toHexString(hashCode()) — rarely useful, so override it.