Skip to main content

equals, hashCode & toString

beginner15 min readLesson 42 of 180

The two-layer equality model, the hash contract collections depend on, and the debugging value of toString.

Equality in Java has two layers, and collections stand on both.

equals(Object) — value equality. The default (from Object) is reference identity; classes override it to compare contents. The contract you must know: reflexive, symmetric (a.equals(b)b.equals(a)), null returns false (never throws — but calling x.equals(null) on your own object should be guarded by an instanceof check).

String a = "yes";
a.equals("yes")                    // true — content
new Point(1, 2).equals(new Point(1, 2))   // record: true — all components

hashCode() — an int summary of the value. THE rule: equal objects must have equal hash codes. If you override equals you must override hashCode consistently, because hash-based collections (HashSet, HashMap) first bucket by hashCode, then confirm with equals. Break the pairing and objects vanish inside sets ("I added it, but contains says false").

class BadPoint {
    final int x, y;
    // ...
    @Override
    public boolean equals(Object o) {
        return o instanceof BadPoint p && p.x == x && p.y == y;
    }
    // hashCode NOT overridden → identity hash → HashSet misbehaves
}

Records and enums get all of this for free — one more reason the modeling tree above prefers them. When you DO hand-write equals (an old-style class), generate hashCode too (your IDE does; Objects.hash(x, y) is the manual form).

toString() is the third leg: every exception message, log line, and debugger view prints it. A class without a real toString debugs badly.

Next: practice — modeling real data.