Week 2 Day 5 — Object: toString, equals, hashCode
Goal
Today I want the three Object methods interviews treat as a filter.
Main questions:
- What does every class inherit from
Object? - What is a useful
toString? - What is the
equals/hashCodecontract? - When should I override them?
- What should JPA entities do? (first look)
1. java.lang.Object
Every class is-a Object. The methods I actually talk about:
| Method | Default | I override when |
|---|---|---|
toString() | ClassName@hexHash | Logs and debug need meaning |
equals(Object) | Identity (==) | The type is a value |
hashCode() | Identity-ish | Whenever I override equals |
getClass() | Runtime class | Rarely override |
clone() | Awkward, Cloneable | Almost never — copy constructor instead |
Records generate equals, hashCode, and toString from components. That is why they fit values.
2. toString
Default toString is useless in logs.
For values, include the data that identifies the value. For entities, include the id and a stable business key — not a lazy collection that triggers extra SQL.
Never put secrets in toString (passwords, tokens, card numbers). Lombok @Data and records will print every component unless I am careful.
Memory sentence:
toStringis for humans and logs. It is not a serializer, and it must not leak secrets.
3. The equals / hashCode contract
If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true.
The reverse is not required: different objects may share a hash (collision). HashMap / HashSet first bucket by hash, then confirm with equals.
Other rules of equals:
- reflexive:
a.equals(a) - symmetric:
a.equals(b)iffb.equals(a) - transitive
- consistent while the objects are not modified
a.equals(null)isfalse, never NPE
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money money)) return false;
return cents == money.cents && currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(currency, cents);
}
If I include a mutable field, a later mutation moves the logical object while the map still looks in the old bucket. The entry is lost.
Memory sentence:
Equal objects share a hash. Mutable keys disappear in a
HashMap.
4. When to override
Do override for value types: Money, Email, records, types I put in a Set or use as map keys.
Do not override for services, Spring beans, or “I want two users with the same name to be equal” when they are different rows.
Entities: equality by database id is tempting and tricky (transient entities have null id). Many teams use a generated UUID assigned in the constructor so equality is stable. Collections week will go deeper. For now: do not use Lombok @Data on entities — it equals on every field, including lazy relations.
instanceof vs getClass() in equals:
instanceofallows a subclass to equal the parent (breaks symmetry if the subclass adds fields).getClass()is stricter.- Records and
finalvalue classes avoid the problem.
5. Spring connection
- Logging a bean: implement
toStringon values, not by dumping a service’s collaborators (they may be proxies). - DTOs as records get a decent
equalsfor tests:assertThat(response).isEqualTo(expected). - Never put a JPA entity in a
HashSetbefore you have chosen an equality rule. - Proxies:
getClass()on a Spring bean may beOrderService$$SpringCGLIB$$0. Compare by type withAopUtilsin framework code; in interviews, say “the runtime class may be a subclass proxy.”
6. Common traps
Trap 1: Override equals and forget hashCode.
Contract broken. Sets and maps misbehave.
Trap 2: @Data on @Entity.
Lazy collections get touched; equality changes after load; hash changes.
Trap 3: user.equals("Ada") with a possible null user.
NPE. Use Objects.equals.
Trap 4: Using == for values “because it is faster.”
It is identity. It is wrong.
Practice Questions and Answers
Question 1
What is the equals / hashCode contract?
Answer:
If two objects are equal according to equals, they must have the same hashCode. HashMap and HashSet rely on that to find the bucket and then the entry. The reverse is not required.
Question 2
When should I override equals?
Answer:
When the type is a value: two instances with the same data should be interchangeable, especially as keys or in sets. I do not override it on services. On entities I choose an id strategy deliberately.
Question 3
Why is a mutable field dangerous in equals / hashCode?
Answer:
The hash is used to place the object in a collection. If a field changes, the object should be in a different bucket, but the collection still looks in the old one. I lose the key.
Question 4
Why avoid Lombok @Data on a JPA entity?
Answer:
@Data includes all fields in equals/hashCode/toString. That can initialize lazy relations, change equality after persistence, and print too much. Prefer id-based equality with a clear rule, or no override yet.
Question 5
What does default equals do?
Answer:
It is identity: this == other. Two new User("Ada") objects are not equal unless I override equals.
Memory sentences
Equal objects must share a hash. Always override
hashCodewithequals.
Values equal by data. Services equal by identity. Entities need an explicit rule.
toStringis for logs. Keep secrets out.
Next: Week 2 Review