equals and hashCode
Where you are: Week 0 review > equals and hashCode
Try This First
A Book overrides equals to compare title and price, but does not override hashCode. Predict the size after adding two equal books to a HashSet:
Book b1 = new Book("Java", 30.00);
Book b2 = new Book("Java", 30.00);
HashSet<Book> set = new HashSet<>();
set.add(b1);
set.add(b2);
System.out.println(set.size());
Reveal
2, even though b1.equals(b2) is true. The set uses hashCode to pick a bucket. The inherited hashCode is identity-based, so the two books land in different buckets, equals is never called between them, and the set keeps both. Overriding equals without hashCode causes this silent loss.
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
See References: Arrows, ==, and Aliasing if either item feels uncertain.
Not sure? Take the 60-second self-check.
Try each from memory, then read the answer under it.
- What does a variable of a class type actually hold? A reference (an arrow) to an object, not the object itself.
- What does
==compare for two object references? Whether they point to the same object, not whether the contents match.
What You Need To Walk In With
Walk into the next class able to state these:
- Override
equalsto compare contents instead of arrows. - Override
hashCodewhenever you overrideequals. The contract: equal objects must return the same hash code. Use the same fields in both. equalsmust take anObjectparameter. Writingequals(Book other)is an overload, not an override, and collections will ignore it.@Overridecatches the mistake at compile time.- A class that overrides
equalsbut nothashCodeloses data silently in aHashSetorHashMap.
You should be able to: override equals and hashCode on the same fields, and explain why both are needed.
How It Works
equals compares contents
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (!(obj instanceof Book other)) { return false; } // false for null and wrong type
return Objects.equals(title, other.title)
&& Double.compare(price, other.price) == 0;
}
The parameter is Object. The instanceof check rejects null and the wrong type, then casts. The body compares the same fields you consider meaningful.
hashCode travels with equals
@Override
public int hashCode() {
return Objects.hash(title, price); // the SAME fields equals compares
}
A hash-based collection calls hashCode to choose a bucket, then equals within that bucket. If two equal objects return different hash codes, they land in different buckets and are never compared, so the set treats them as distinct. Equal objects must share a hash code.
Override, not overload
public boolean equals(Book other) { ... } // WRONG: this is an overload
This does not override Object.equals(Object); it is a separate method with a different parameter type. Collections call equals through an Object reference, so they use the inherited version and ignore yours. The bug hides in tests that call b1.equals(b2) with Book references, where the compiler picks the overload. Writing @Override above the method forces a compile error unless the signature is the real equals(Object).
Worked Example: Predict, Then Check
A Book correctly overrides both equals and hashCode on title and price. Predict:
HashSet<Book> set = new HashSet<>();
set.add(new Book("Java", 30.00));
System.out.println(set.contains(new Book("Java", 30.00)));
Reveal
true. Equal fields give the same hash code (same bucket) and equals returns true, so the set finds the matching book. This is the behavior the inherited hashCode would have broken.
A Common Mistake
Overriding equals and forgetting hashCode is the classic silent bug: direct b1.equals(b2) calls look correct, but a HashSet or HashMap stores duplicates and reports contains as false for an equal object. Override both, on the same fields, every time. (Source: BJP (Reges and Stepp), Ch 8; Effective Java, Items 10 and 11.)
Go Deeper (optional)
For the curious: the contract runs one direction only. Equal objects must share a hash code, but two unequal objects may share one (a collision), and the collection handles that by checking equals within the bucket. That is why a weak hashCode (say, always returning 0) is correct but slow: every object lands in one bucket, turning fast lookups back into a linear scan.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
A Book overrides equals but not hashCode. Two equal books are added to a HashSet. What is set.size()?
When you override equals, what else must you override?
What is the parameter type of a correct equals override?
How does @Override help when writing equals?