Java equals() e hashCode() métodos estão presentes na classe Object. Portanto, toda classe Java recebe a implementação padrão de equals() e hashCode(). Neste post, vamos analisar em detalhes os métodos java equals() e hashCode().
Java equals()
A classe Object define o método equals() da seguinte forma:
public boolean equals(Object obj) {
return (this == obj);
}
De acordo com a documentação Java do método equals(), qualquer implementação deve aderir aos seguintes princípios.
- Para qualquer objeto x,
x.equals(x)
deve retornartrue
. - Para dois objetos x e y,
x.equals(y)
deve retornartrue
se e somente sey.equals(x)
retornartrue
. - Para múltiplos objetos x, y e z, se
x.equals(y)
retornatrue
ey.equals(z)
retornatrue
, entãox.equals(z)
deve retornartrue
. - Múltiplas invocações de
x.equals(y)
devem retornar o mesmo resultado, a menos que alguma das propriedades do objeto seja modificada e está sendo usada na implementação deequals()
. - A implementação do método equals() da classe Object retorna
true
apenas quando ambas as referências estão apontando para o mesmo objeto.
Java hashCode()
O método nativo Java Object hashCode() retorna o valor do código hash inteiro do objeto. O contrato geral do método hashCode() é:
- Múltiplas invocações de hashCode() devem retornar o mesmo valor inteiro, a menos que a propriedade do objeto que está sendo usada no método equals() seja modificada.
- O valor do código hash de um objeto pode mudar em múltiplas execuções da mesma aplicação.
- Se dois objetos são iguais de acordo com o método equals(), então seus códigos hash devem ser iguais.
- Se dois objetos são diferentes de acordo com o método equals(), seus códigos hash não são obrigados a ser diferentes. Seu valor de código hash pode ou não ser igual.
Importância do método equals() e hashCode()
Os métodos hashCode() e equals() do Java são usados em implementações baseadas em tabelas de hash em Java para armazenar e recuperar dados. Eu expliquei detalhadamente em Como o HashMap funciona em Java? A implementação de equals() e hashCode() deve seguir essas regras.
- Se
o1.equals(o2)
, entãoo1.hashCode() == o2.hashCode()
deve sempre sertrue
. - Se
o1.hashCode() == o2.hashCode
é verdadeiro, isso não significa queo1.equals(o2)
serátrue
.
Quando substituir os métodos equals() e hashCode()?
Quando substituímos o método equals(), é quase necessário substituir também o método hashCode(), para que o contrato entre eles não seja violado pela nossa implementação. Note que o programa não lançará exceções se o contrato entre equals() e hashCode() for violado; se não tiver a intenção de usar a classe como chave em uma tabela de hash, isso não causará problemas. No entanto, se a intenção for usar a classe como chave em uma tabela de hash, é imprescindível substituir ambos os métodos, equals() e hashCode(). Vamos ver o que acontece quando confiamos na implementação padrão dos métodos equals() e hashCode() e usamos uma classe personalizada como chave em um HashMap.
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// Métodos getter e setter
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
Integer value = hm.get(dk);
System.out.println(value);
}
private static Map<DataKey, Integer> getAllData() {
Map<DataKey, Integer> hm = new HashMap<>();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
Ao executarmos o programa acima, será impresso null
. Isso ocorre porque o método hashCode() padrão da classe Object é utilizado para encontrar o bucket onde a chave deve ser procurada. Como não temos acesso às chaves do HashMap e estamos criando a chave novamente para recuperar os dados, perceberemos que os valores de hash code de ambos os objetos são diferentes e, portanto, o valor não é encontrado.
Implementando os métodos equals() e hashCode()
Podemos definir nossa própria implementação dos métodos equals() e hashCode(), mas se não os implementarmos com cuidado, pode haver problemas estranhos em tempo de execução. Felizmente, a maioria das IDEs hoje em dia oferece maneiras de implementá-los automaticamente e, se necessário, podemos alterá-los de acordo com nossos requisitos. Podemos usar o Eclipse para gerar automaticamente os métodos equals() e hashCode(). Aqui estão as implementações automáticas dos métodos equals() e hashCode().
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataKey other = (DataKey) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
Note que ambos os métodos equals() e hashCode() estão usando os mesmos campos para os cálculos, para que seu contrato permaneça válido. Se você executar o programa de teste novamente, obteremos o objeto do mapa e o programa imprimirá 10. Também podemos usar o Project Lombok para gerar automaticamente as implementações dos métodos equals() e hashCode().
O que é Colisão de Hash
Em termos muito simples, as implementações de tabela de hash em Java usam a seguinte lógica para operações de get e put.
- Primeiro identifique o “Bucket” a ser usado usando o código hash da “chave”.
- Se não houver objetos presentes no bucket com o mesmo código hash, então adicione o objeto para a operação de put e retorne null para a operação de get.
- Se houver outros objetos no balde com o mesmo código hash, o método “key” equals entra em jogo.
- Se equals() retornar true e for uma operação de colocação, então o valor do objeto é substituído.
- Se equals() retornar false e for uma operação de colocação, uma nova entrada é adicionada ao balde.
- Se equals() retornar true e for uma operação de obtenção, então o valor do objeto é retornado.
- Se equals() retornar false e for uma operação de obtenção, então null é retornado.
A imagem abaixo mostra os itens de um balde de HashMap e como seus métodos equals() e hashCode() estão relacionados. O fenômeno quando duas chaves têm o mesmo código hash é chamado de colisão de hash. Se o método hashCode() não for implementado corretamente, haverá um maior número de colisões de hash e as entradas do mapa não serão distribuídas corretamente, causando lentidão nas operações de obtenção e colocação. Essa é a razão para o uso de números primos na geração do código hash, para que as entradas do mapa sejam distribuídas adequadamente entre todos os baldes.
O que acontece se não implementarmos tanto o hashCode() quanto o equals()?
Já vimos anteriormente que se o hashCode() não for implementado, não poderemos recuperar o valor, pois o HashMap usa o código hash para encontrar o compartimento em que procurar a entrada. Se apenas usarmos o hashCode() e não implementarmos o equals(), então o valor também não será recuperado porque o método equals() retornará falso.
Melhores práticas para implementar os métodos equals() e hashCode()
- Use as mesmas propriedades tanto nas implementações dos métodos equals() quanto hashCode(), para que o contrato deles não seja violado quando qualquer propriedade for atualizada.
- É melhor usar objetos imutáveis como chave da tabela hash para que possamos armazenar em cache o código hash em vez de calculá-lo em cada chamada. É por isso que String é uma boa candidata para chave de tabela hash, porque é imutável e armazena em cache o valor do código hash.
- Implemente o método hashCode() de forma que o menor número possível de colisões de hash ocorra e as entradas sejam distribuídas uniformemente em todos os compartimentos.
Você pode baixar o código completo do nosso Repositório no GitHub.
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode