Equality, Hashing, and Immutability¶
Equality contracts¶
For non-null references, equals must be reflexive, symmetric, transitive, and
consistent, and must return false for null. Equal objects must have equal hash
codes during an execution.
Identity (==) asks whether references denote the same object. Value equality
(equals) asks whether objects represent the same value under their contract.
record BookId(String value) {
BookId {
Objects.requireNonNull(value, "value");
if (value.isBlank()) throw new IllegalArgumentException("blank id");
}
}
A record derives equality and hashing from its components. The record is only as deeply immutable as those components; a component referring to a mutable list would still require defensive copying.
Designing immutable classes¶
- make invariant-bearing state private and final;
- validate construction;
- do not expose mutable internals;
- copy mutable inputs and outputs where ownership is not transferred;
- prevent subclass mutation when the contract requires it.
Immutability simplifies reasoning, safe publication, hashing, and sharing across threads. It does not automatically make an operation atomic across multiple immutable values.
The practical guide expands these rules to shallow copies, unmodifiable views, records, builders, snapshots, and cache keys.
Comparator consistency¶
Sorted sets and maps use comparison to determine key distinctness. If a
comparator reports zero for objects that equals considers different, the
collection may appear to “lose” a key. Either make the ordering consistent with
equality or document the alternative equivalence relation.
Exercises¶
- Explain why subclass-based equality often breaks symmetry.
- Make a class containing a
List<String>deeply immutable. - Describe the failure caused by mutating a hash key after insertion.