Skip to content

Why hashCode Was Not Optional

The same equals, the same two defects, and one missing hashCode method is the entire difference between a Set size of 1 and a duplicate defect filed twice.

2 min read

Now the payoff for Chapter 7, and the clearest demonstration in this book of why equals and hashCode must travel together.

Here is a Defect class with a correct equals comparing by id — and a matching hashCode. Two defects with the same id are added to a Set:

class Defect {
    String id;
    Defect(String id) { this.id = id; }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        return id.equals(((Defect) o).id);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
Set<Defect> seen = new HashSet<>();
seen.add(new Defect("DEF-101"));
seen.add(new Defect("DEF-101"));
System.out.println("Set size: " + seen.size());
Set size: 1

Correct — the duplicate was recognized and refused. Now delete the hashCode method and change nothing else. Same equals, same test:

Set size: 2

The very same objects, by the very same equals, are now stored twice. This is not a subtle difference; it is a duplicate defect filed in your report.

The reason is how a HashSet and HashMap actually work. They do not compare your object against every item — that would be slow. They first call hashCode() to compute a number, and use it to jump straight to a small group of candidates. Only within that group do they call equals. So when hashCode is missing, Java uses the default, which is based on object identity. The two identical defects get different numbers, land in different groups, and equals is never even called between them.

That is the whole rule, now with a reason attached: objects that are equal must return the same hashCode. Break it and your objects work fine in a List and silently misbehave in every Set and Map. It is also why String is such a safe key — it has a well-built equals and hashCode already. This also answers whether one of your own classes can be a Map key: yes, and that is exactly when the equals/hashCode rule becomes critical. A key type without both is a key you cannot reliably find again — most of the time a String or a number is the simpler, safer key.

Promise Note #3 — opened in Chapter 5, when lists could not do lookups or refuse duplicates. Paid here, with the Map and the Set. That was the last note outstanding: #1 (the main method) was paid in Chapter 8, #2 (arrays) in Chapter 5, and #4 (equals and hashCode) in Chapter 7. The book owes you nothing.

Every idea in this container has resurfaced from somewhere earlier in this book. The closing bug hunt proves it — three wrong lines, and not one of them is a new mistake.

This article is part of Automation Foundations, in the Automation Engineering track. See the full sequence to find what comes next.