equals() and hashCode() Contract
Object.equals() tests logical equality; == tests reference identity. The contract: if a.equals(b) then a.hashCode() == b.hashCode(). The default Object.equals() is reference identity and hashCode() is derived from the object's identity.
equals/hashCode = filing cabinet system. hashCode picks the drawer (bucket), equals checks the exact folder inside. If two identical files land in different drawers, you'll never find the duplicate.
Full contract: reflexive (a.equals(a)), symmetric (a.equals(b) ↔ b.equals(a)), transitive, consistent (same result unless fields change), and a.equals(null) == false. If you override equals(), you MUST override hashCode(). Failing to do so causes objects to be lost in HashMaps and HashSets — they can be put in but not retrieved because lookup uses hashCode() to find the bucket. Use Objects.equals() and Objects.hash() to handle nulls safely.
A common subtle bug: using a mutable field in hashCode() then modifying that field while the object is in a HashSet — the object ends up in the wrong bucket and can never be found or removed (memory leak). Effective Java (Bloch) recommends a prime-number-based polynomial hash to distribute values. Records in Java 16+ auto-generate equals/hashCode based on all components. Lombok's @EqualsAndHashCode has an of/exclude attribute to control which fields participate. In JPA/Hibernate, entities should use surrogate keys (database IDs) in equals/hashCode to avoid issues with transient entities that have no ID yet.
The contract is: if a.equals(b) is true, then a.hashCode() must equal b.hashCode(). The inverse is not required — hash collisions are legal. Breaking this contract causes silent data loss in hash-based collections: put() uses hashCode() to find the bucket, then equals() to find the key. If hashCode() is inconsistent with equals(), gets and removes miss the stored entry. You must override both methods together — never just one.
Overriding equals() without overriding hashCode() (or vice versa) compiles fine and produces no runtime error until you use the class in a HashMap or HashSet, at which point objects appear to vanish.