Cuando anula equals() para definir igualdad lógica, debe también anular hashCode() — porque las colecciones basadas en hash (HashMap, ) se basan en el contrato de que . Romper esto conduce a errores sutiles y difíciles 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
Sin anulación, dos objetos con contenido idéntico no son "iguales" — generalmente no lo que desea 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()
}
El contrato: si a.equals(b) es verdadero, entonces a.hashCode() == b.hashCode() DEBE ser verdadero.
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 primero usa hashCode() para encontrar el bucket correcto, luego equals() dentro del mismo. Con el hashCode() predeterminado (basado en identidad), p1 y p2 terminan en buckets diferentes, por lo que contains nunca los compara — el conjunto cree que son diferentes. Esto produce errores desconcertantes: duplicados en un Set, búsquedas fallidas en el mapa, 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 (y generación IDE / Lombok) produce equals/hashCode correcto y consistente para usted.
El contrato equals/hashCode es una de las reglas más importantes — y más comúnmente violadas — de Java.
Anular equals() sin hashCode() rompe las colecciones basadas en hash de formas que son difíciles de depurar: los objetos que considera iguales se "pierden" en HashMap/HashSet (búsquedas fallidas, duplicados fantasma) porque se codifican en buckets diferentes.
Como estas colecciones son omnipresentes, toda clase de tipo de valor utilizada como clave o elemento del conjunto debe anular ambas de manera consistente.
Entender por qué (el mecanismo de búsqueda bucket-luego-igualdad) — y usar records o código generado para hacerlo bien — es esencial para un comportamiento correcto y un tema clásico de entrevista que revela la comprensión real de cómo funcionan las colecciones Java.