W3docs

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:

  • Reflexivax.equals(x) é verdadeiro.
  • Simétricax.equals(y) se e somente se y.equals(x).
  • Transitiva — se x.equals(y) e y.equals(z), então x.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:

java— editable, runs on the server

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 == o trata o caso comum rapidamente.
  • Verificação de tipo com binding. instanceof Point p rejeita 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.compare para 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:

  • instanceof permite 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

java— editable, runs on the server

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.

Prática

Prática
Por que é um bug sobrescrever `equals` sem também sobrescrever `hashCode`?
Por que é um bug sobrescrever `equals` sem também sobrescrever `hashCode`?
Was this page helpful?