Objects, Classes & OOP Design
Object Contracts: equals, hashCode, toString, Comparable
The methods every class inherits from
Object come with strict contracts. Get equals/hashCode wrong and hash-based collections silently misbehave; get compareTo wrong and sorted collections do. Records give you all of it for free.equalsmust be reflexive, symmetric, transitive, consistent, and non-null- Equal objects must have equal hash codes — override both or neither
- A mutated key whose hashCode changes is lost to its
HashMap - Always override
toStringfor debuggability (EJ 12) compareToshould be consistent withequals; useComparatorcombinators- Records generate correct
equals/hashCode/toStringautomatically
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Point p)) return false; // also rejects null
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}Why the pairing matters: HashMap first locates the bucket by hashCode, then confirms with equals (Hashing Internals). Equal-but-different-hash objects end up in different buckets — map.get(key) returns null for a key that is "equal" to one inside. This is the single most common cause of mysterious collection bugs.
Ordering with Comparable and Comparator
static final Comparator<Employee> BY_PAY_THEN_NAME =
Comparator.comparingDouble(Employee::getSalary).reversed()
.thenComparing(Employee::getName);
employees.sort(BY_PAY_THEN_NAME);Implement Comparable when the class has a natural order (String, LocalDate, BigDecimal do). Never compute a - b for int comparison — it overflows; use Integer.compare(a, b). Sorted collections use compareTo instead of equals for membership (Sorted Collections), so inconsistency between the two produces different behavior in TreeSet vs HashSet — BigDecimal 2.0/2.00 being the classic demonstration.