El Contrato Sagrado: equals() y hashCode()
Si dos objetos son iguales según equals(), obligatoriamente deben generar el mismo hashCode numérico. Si esto se rompe, las tablas hash no funcionarán.
En una línea Sobrescribir ambos usando Objects.equals(id, otro.id) y Objects.hash(id).
public final class Email {
private final String valor;
public Email(String valor) {
this.valor = valor.strip().toLowerCase(Locale.ROOT);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Email otro)) return false; // pattern matching de instanceof
return valor.equals(otro.valor);
}
@Override
public int hashCode() {
return valor.hashCode(); // mismos campos que equals
}
}
Set<Email> vistos = new HashSet<>();
vistos.add(new Email("Ana@Acme.com"));
System.out.println(vistos.contains(new Email("ana@acme.com"))); // true
// Si borras hashCode(): contains devuelve false casi siempre,
// porque cada instancia tiene un hash de identidad distinto y cae en otro cubo.
Cómo funciona por dentro
equals define cuándo dos objetos representan lo mismo; hashCode devuelve un entero que las tablas hash usan para decidir en qué cubo guardar el objeto. El contrato dice: si a.equals(b) es true, entonces a.hashCode() == b.hashCode(). Lo contrario no es obligatorio: dos objetos distintos pueden compartir hash (colisión).
Así busca un HashMap: calcula hashCode() de la clave, lo mezcla y elige un cubo. Dentro del cubo recorre los elementos comparando primero el hash y después con equals. Si sobrescribes equals sin hashCode, dos objetos iguales caerán en cubos distintos y el mapa nunca encontrará el segundo. Desde Java 8, un cubo con muchas colisiones se convierte en un árbol para evitar degradarse a O(n).
equals además debe ser reflexivo, simétrico, transitivo, consistente y devolver false con null. Usa los mismos campos en ambos métodos, Objects.equals y Objects.hash para no equivocarte con los null, o deja que un record los genere.
records lo implementan automáticamente y de forma óptima.
Se conecta con HashMap InternoHashSetIdentidad de Objetos