Java › equals(), hashCode() & toString() Contracts
What are the five formal properties an equals() implementation must satisfy according to its documented contract (reflexive, symmetric, transitive, consistent, and null-handling)?
IntermediateAnswer
Reflexive: x.equals(x) must always be true; Symmetric: x.equals(y) must equal y.equals(x); Transitive: if x.equals(y) and y.equals(z), then x.equals(z) must also be true; Consistent: repeated calls to x.equals(y) must return the same result as long as neither object's relevant fields change; and x.equals(null) must always return false -- violating any of these (a surprisingly easy mistake, especially symmetry when comparing objects of different but related types) causes subtle, hard-to-diagnose bugs in collections and any code relying on equals() behaving predictably.
public class CaseInsensitiveString {
private final String value;
public boolean equals(Object o) {
if (o instanceof String s) return value.equalsIgnoreCase(s); // BROKEN: violates symmetry!
if (o instanceof CaseInsensitiveString c) return value.equalsIgnoreCase(c.value);
return false;
}
// "ABC".equals(myCaseInsensitiveString) uses String's equals(), which returns false
// but myCaseInsensitiveString.equals("ABC") returns true -- SYMMETRY VIOLATEDReal-world example A library's CaseInsensitiveString class comparing itself against plain String objects violates the symmetry contract (a.equals(b) true but b.equals(a) false), causing confusing, order-dependent behavior when such instances are placed in a HashSet alongside plain Strings, a bug traced directly back to this contract violation.