intermediateFree

The equals Without hashCode Trap

Why your HashSet loses the object

The equals Without hashCode Trap

Override equals() but not hashCode() and hash-based lookups break - equal objects can land in different buckets.

The narrated version of this skribble — tap play for the full-size video.

0:00 / 1:23

What You'd Expect

Override equals so two Points with the same x,y are equal, then add one to a HashSet.

  • You'd expect set.contains(new Point(1,2)) to find the equal one
  • equals() says they're the same, so the set should agree
  • Value equality ought to be enough for lookups

What Actually Happens

contains() usually returns false - the set seems to 'lose' the equal object.

  • Even though equals() would say true, the lookup never gets that far
  • It's unreliable, not always-false: it only 'works' if the buckets happen to collide
  • The same bug breaks HashMap keys and removal too

Why It Happens

Hash collections find by hashCode first, then equals within a bucket.

  • Contract: equal objects MUST return equal hashCodes
  • Default Object.hashCode is identity-based - different per instance
  • Different hash → different bucket → equals() is never even called

The Fix & Best Practice

Always override hashCode whenever you override equals - as a pair.

  • @Override public int hashCode() { return Objects.hash(x, y); }
  • Use the SAME fields in equals and hashCode
  • Better: use a record - it generates consistent equals + hashCode for you
#java#gotchas#equals#hashcode