Java equals() e hashCode()
Saiba como sobrescrever equals() e hashCode() corretamente em Java para suportar coleções e igualdade baseada em valor.
equals e hashCode são os dois métodos de Object dos quais coleções baseadas em hash — HashMap, HashSet, LinkedHashMap, qualquer estrutura baseada em hashing — dependem silenciosamente. Implemente-os corretamente e seus objetos se comportam como valores: set.contains(point) encontra o ponto independentemente de qual instância new Point(3, 4) você passar. Implemente-os errado e você terá duplicatas em sets, chaves ausentes em maps, e bugs que só aparecem sob carga.
O padrão herdado de Object compara identidade: duas referências são iguais apenas quando apontam para o mesmo objeto. Isso é adequado para coisas como conexões com banco de dados, em que cada instância é um recurso distinto. Para classes que representam valores — dinheiro, pontos, nomes, datas — você quase sempre deseja igualdade por conteúdo, e isso significa sobrescrever os dois métodos juntos.
O contrato
equals deve satisfazer quatro regras:
- Reflexiva —
x.equals(x)é verdadeiro. - Simétrica —
x.equals(y)se e somente sey.equals(x). - Transitiva — se
x.equals(y)ey.equals(z), entãox.equals(z). - Consistente — chamadas repetidas com campos inalterados retornam a mesma resposta.
Além disso: x.equals(null) deve retornar false, nunca lançar uma exceção.
hashCode tem uma regra que o vincula a equals:
- Objetos iguais devem ter códigos hash iguais. Objetos diferentes podem compartilhar um código hash (colisões são permitidas, mas prejudicam o desempenho).
Essa única regra é o motivo pelo qual não se pode sobrescrever um sem o outro. Se a.equals(b) mas a.hashCode() != b.hashCode(), HashSet os coloca em baldes diferentes, contains encontra o errado, e você tem uma duplicata fantasma.
Observe o contrato sendo violado
Esta classe sobrescreve equals mas esquece hashCode, portanto herda o hash baseado em identidade de Object. Os dois objetos são "iguais", mas ficam em baldes diferentes — contains não consegue encontrar o que você acabou de adicionar:
equals diz que os objetos são iguais, mas o set não consegue encontrar o segundo. Sobrescreva hashCode para corresponder e a busca funcionará.
Anatomia de um equals correto
Um equals funcional segue uma estrutura padrão:
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}Passo a passo:
- Curto-circuito de identidade.
this == otrata o caso comum rapidamente. - Verificação de tipo com binding.
instanceof Point prejeita nulls e tipos errados em uma única expressão e vincula a referência com o tipo reduzido. - Comparação de campos. Use
==para primitivos,Objects.equals(a, b)para referências que podem ser null,Float.compare/Double.comparepara floats.
Objects.hash(...) constrói um hash a partir de uma lista de campos. É ligeiramente mais lento do que código XOR/multiplicação escrito à mão, mas é correto e inequívoco.
getClass ou instanceof?
Duas correntes:
instanceofpermite que uma instância de subclasse seja igual a uma instância pai se o conjunto de campos comparados for o mesmo. Ligeiramente mais flexível.getClass()exige a classe em tempo de execução exata. Mais fácil de manter simétrico em hierarquias, mas quebra a substituibilidade.
Para a maioria das classes que representam valores, o caminho mais simples é tornar a classe final e usar instanceof. Sem final, misturar os dois estilos em uma hierarquia é onde a maioria dos bugs de igualdade ocorre. Records contornam completamente essa decisão — são implicitamente finais e o equals gerado usa uma verificação de tipo exata.
Campos de ponto flutuante
Não use == em campos double ou float — o valor +0.0 é igual a -0.0 com ==, mas Double.compare os trata de forma diferente, e NaN == NaN é false. Double.compare(a, b) == 0 e Float.compare fornecem a resposta consistente exigida pelo contrato.
Arrays
Object.equals em um array compara referências, não conteúdos. Use Arrays.equals(a, b) para arrays unidimensionais, Arrays.deepEquals para arrays multidimensionais. Da mesma forma, use Arrays.hashCode / Arrays.deepHashCode no hashCode.
Mutabilidade é hostil a coleções baseadas em hash
Se você mutar um campo que faz parte de equals/hashCode após inserir o objeto em um HashSet, o balde em que o set o colocou deixa de corresponder ao novo hash — e o objeto se torna inacessível via contains. A regra mais segura: os campos usados em equals devem ser final. Se isso não for possível, nunca insira o objeto em uma coleção baseada em hash.
Não escreva nenhum dos dois à mão para classes de dados simples
Se a classe é um contêiner de dados puro, prefira um record — o compilador gera equals e hashCode corretos para você, e os dois sempre permanecerão sincronizados conforme os campos mudam. Se você não puder usar um record, o comando "generate equals/hashCode" da sua IDE é a próxima melhor opção.
Um exemplo completo
O que vem a seguir
equals permite que seus objetos se comparem; toString permite que eles se descrevam. O próximo capítulo trata de sobrescrever toString para produzir uma saída realmente útil em logs, mensagens de erro e depuradores. Continue para Método toString do Java.