Quando você faz override de equals() para definir igualdade lógica, você deve também fazer override de hashCode() — porque coleções baseadas em hash (HashMap, ) dependem do contrato que . Quebrar isso leva a bugs sutis e difíceis de encontrar.
Quando você faz override de equals() para definir igualdade lógica, você deve também fazer override de hashCode() — porque coleções baseadas em hash (HashMap, ) dependem do contrato que . Quebrar isso leva a bugs sutis e difíceis de encontrar.
HashSet// by default, equals() compares IDENTITY (same object?), hashCode() is based on memory address
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30);
p1.equals(p2); // false by default — different objects, even with same data
Sem override, dois objetos com conteúdo idêntico não são "iguais" — geralmente não é o que você quer para objetos de valor.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name); // compare by CONTENT
}
@Override
public int hashCode() {
return Objects.hash(name, age); // MUST be consistent with equals()
}
O contrato: se a.equals(b) é verdadeiro, então a.hashCode() == b.hashCode() deve ser verdadeiro.
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30); // equal by our equals()
Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // ❌ likely FALSE — even though p1.equals(p2)!
Por quê? HashMap/HashSet primeiro usam hashCode() para encontrar o bucket correto, depois equals() dentro dele. Com o hashCode() padrão (baseado em identidade), p1 e p2 caem em buckets diferentes, então contains nunca chega a compará-los — o set acha que são diferentes. Isso produz bugs desconcertantes: duplicatas em um Set, lookups de map falhando, etc.
1. equal objects → equal hash codes (REQUIRED for correctness)
2. unequal objects MAY have the same hash (collisions are allowed)
3. hashCode() must be consistent (same object → same code, unchanged)
4. equals() must be reflexive, symmetric, transitive, consistent
record Person(String name, int age) {} // records auto-generate equals/hashCode/toString!
Java records (e geração por IDE / Lombok) produzem equals/hashCode corretos e consistentes para você.
O contrato equals/hashCode é uma das regras mais importantes do Java — e mais frequentemente violadas.
Fazer override de equals() sem hashCode() quebra coleções baseadas em hash de formas que são difíceis de debugar: objetos que você considera iguais ficam "perdidos" em HashMap/HashSet (lookups falhando, duplicatas fantasma) porque fazem hash para buckets diferentes.
Como essas coleções são onipresentes, toda classe de tipo de valor usada como chave ou elemento de set deve fazer override de ambos consistentemente.
Entender por quê (o mecanismo de lookup bucket-depois-equals) — e usar records ou código gerado para acertar — é essencial para comportamento correto e um tópico clássico de entrevista que revela compreensão real de como as coleções Java funcionam.