Show a correct, contract-honoring equals()/hashCode() pair and explain how each line keeps the two consistent.
answer
- Field-set parity: hashCode fields ⊆ equals fields (ideally equal)
- equals skeleton: this== , type guard, cast, compare fields null-safe
- Objects.hash(...) / Objects.equals(...) for the safe, terse form
- 31-multiplier idiom = order-sensitive, (r<<5)-r
- Prefer a record; only hand-roll for hot-path allocation
basics
~20 sCompare the same fields in both methods. In equals(): check identity, check the type, then compare each field (null-safe with Objects.equals). In hashCode(): combine those same fields with Objects.hash(...). Same fields in => consistent results out.
solid answer
~40 sWrite equals() to (1) short-circuit on this == o, (2) reject a non-matching type (getClass() check or instanceof, knowing the trade-off), and (3) compare each significant field, using Objects.equals for object fields and == for primitives (with Float/Double.compare for floating point). Write hashCode() to combine *the exact same fields* via Objects.hash(f1, f2, ...) — or build it manually with the 31-multiplier idiom (result = 31*result + Objects.hashCode(field)). The single rule that keeps the contract is field-set parity: hashCode() must use a subset of, ideally exactly, the fields equals() uses, so equal objects always hash identically. Excluding a field from hashCode is allowed (still correct, fewer-but-larger buckets); including a field in hashCode that equals() ignores breaks the contract. In modern Java, prefer a record, which generates both correctly from the components.
code
java · 23 linesimport java.util.Objects;
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y); // same fields as equals()
}
}
// record Point(int x, int y) {} // generates both correctly for freego deeper
Can copy the standard equals/hashCode skeleton and knows to use Objects.hash with the same fields. May not articulate the subset rule.
Writes both methods from scratch with null-safe field comparison and explains field-set parity as the consistency guarantee.
Reasons about getClass-vs-instanceof trade-offs (symmetry vs Liskov), float/double comparison, the 31-multiplier rationale, and when to exclude fields from hashCode.
Sets team policy (records/Lombok/IDE generation, immutable keys, no hand-edited single method), and weighs hash distribution and allocation cost of Objects.hash on hot paths.
## Goal A correct pair must satisfy two things at once: the **equals contract** (reflexive, symmetric, transitive, consistent, `x.equals(null)` is false) and the **interdependence rule** (equal objects return equal hash codes). The practical key to the second is **field-set parity**: build the hash from the same fields you compare. ## A worked example ```java import java.util.Objects; public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; // 1 if (o == null || getClass() != o.getClass()) return false; // 2 Point p = (Point) o; // 3 return x == p.x && y == p.y; // 4 } @Override public int hashCode() { return Objects.hash(x, y); // 5 } } ``` ### Line-by-line 1. **`this == o`** — a fast identity short-circuit. An object is always equal to itself (reflexivity), and the common "same reference" case returns immediately without comparing fields. 2. **Type guard** — `o == null` returns false (the contract demands `x.equals(null) == false`), and `getClass() != o.getClass()` rejects a different runtime type. Using `getClass()` makes equality *exact-class*; using `instanceof` instead would allow subclasses but can break **symmetry** if a subclass adds state. Pick one deliberately; for a `final` class it doesn't matter. 3. **Cast** — safe now that the type is confirmed. 4. **Field comparison** — compare every *significant* field. Primitives use `==`; object fields should use `Objects.equals(a, b)` (null-safe); `float`/`double` should use `Float.compare`/`Double.compare` to handle `NaN` and `-0.0` correctly. 5. **hashCode from the SAME fields** — `Objects.hash(x, y)` combines exactly the fields used in step 4. This is the line that guarantees the interdependence rule: any two `Point`s that are `equals()`-equal have identical `x` and `y`, so `Objects.hash` returns the same int for both. ## The manual idiom (what `Objects.hash` does conceptually) ```java @Override public int hashCode() { int result = Integer.hashCode(x); result = 31 * result + Integer.hashCode(y); return result; } ``` The multiplier **31** is an odd prime; multiplying by it before adding the next field's hash makes the result **order-sensitive** (so `(1,2)` and `(2,1)` differ) and spreads bits well, while `31 * r` compiles to a cheap `(r << 5) - r`. `Objects.hash(...)` is the readable equivalent (it boxes its args into a varargs array, a minor cost on hot paths — that's the only reason to hand-roll). ## The one rule that keeps them consistent > The set of fields feeding `hashCode()` must be a **subset of** (ideally **equal to**) the fields feeding `equals()`. - **Field in equals but NOT in hashCode:** still **correct** — two equal objects might differ only in that field, but they'll still share a hash; you just get coarser bucketing (more collisions). Legal, occasionally a deliberate performance choice. - **Field in hashCode but NOT in equals:** **broken** — two `equals()`-equal objects can differ in that field and thus hash differently, violating the contract and making them unretrievable. **Never do this.** ## Modern shortcuts - A **`record`** (`record Point(int x, int y) {}`) auto-generates `equals`, `hashCode`, and `toString` from its components with perfect field-set parity — the preferred choice for value types. - IDE generation ("Generate equals() and hashCode()") and Lombok `@EqualsAndHashCode` produce the paired methods together, which is exactly why you should generate both at once rather than hand-edit one. ## Mutable-field caveat If a field used in `hashCode()` can change after the object is used as a key, the key's bucket becomes stale and it goes missing. Prefer **immutable** fields (all `final` here) for anything that participates in `hashCode()`.
- Is it ever acceptable to exclude a field from hashCode() that equals() uses?Yes, and it stays correct: equal objects can differ in that field but still hash the same, so the contract holds. You only sacrifice distribution (bigger buckets, more collisions). The reverse — a field in hashCode but not equals — is what's forbidden.
- Why prefer getClass() over instanceof in the type check, or vice versa?getClass() enforces exact-class equality, which preserves symmetry/transitivity even with subclasses but violates Liskov substitutability (a subclass instance can never equal a superclass one). instanceof allows subclass comparison but can break symmetry if a subclass adds equality-relevant state. For final classes (or records) it's moot; otherwise choose based on whether subclasses should ever be equal to the base.
saying these in an interview costs you the question
- Hashing a field that equals() ignores (breaks the contract)
- Forgetting the o == null / type check, causing ClassCastException or NPE
- Using == to compare object (reference) fields instead of equals/Objects.equals
- Comparing float/double with == instead of Float/Double.compare
- Including mutable fields in hashCode for objects used as map keys