Desvendando os métodos equals e hashcode

Muitas vezes no dia-a-da do desenvolvimento Java sobrescrevemos os métodos equals e hashcode em nossas classes por várias razões, dentre elas porque alguma IDE o faz de maneira automática ou mesmo porque seguimos recomendações de boas práticas encontradas em artigos, livros, blogs, etc.

Porém não paramos para analisar qual a utilidade desses métodos e quais são os seus reais benefícios para a aplicação.

Pensando nisso, neste post iremos demonstrar o funcionamento desses dois importantes métodos e como podem ser extramente úteis quando trabalhamos com as Coleções (Collections) do Java.

Para começar, vamos criar a classe Pessoa que terá apenas o atributo Nome como mostra a Listagem 1. Essa classe possui também o método toString() para mostramos o valor do atributo Nome dos objetos do tipo Pessoa.

public class Pessoa {
  private String nome;

  /*Getter and Setter omitidos*/

  @Override
  public String toString() {
    return "Pessoa [nome=" + nome + "]";
  }
}
Listagem 1 – Classe Pessoa com atributo Nome

Muito bem, agora vamos criar dois objetos do tipo Pessoa com o mesmo Nome e verificar se esses objetos são iguais ou não. Para fazer isso, criamos o código da Listagem 2.

Pessoa maria = new Pessoa();
maria.setNome("Maria");
		
Pessoa maria2 = new Pessoa();
maria2.setNome("Maria");
		
if (maria.equals(maria2)) {
   System.out.println("São a mesma pessoa");
} else {
   System.out.println("Não são a mesma pessoa");
}
Listagem 2 – Comparando dois objetos do tipo Pessoa

Ao executar esse código o resultado será: Não são a mesma pessoa. Ambos os objetos (maria e maria2) possuem o mesmo valor para o atributo Nome e o método equals() retornou falso. Isso aconteceu porque a classe Pessoa não sobrescreveu o método equals() da classe Object a fim de definir uma lógica de comparação entre objetos do tipo Pessoa.

Sendo assim, vamos sobrever o método equals na classe Pessoa como mostra a Listagem 3.

 
public class Pessoa {
  private String nome;
  
  @Override
  public String toString() {
    return "Pessoa [nome=" + nome + "]";
  }
  
  @Override
  public boolean equals(Object obj) {
    Pessoa p = (Pessoa)obj;
    return this.nome.equals(p.getNome());
  }
  
  /*Getter and Setter omitidos*/
}
Listagem 3 – Implementando método equals() na classe Pessoa

No método equals() da classe Pessoa realizamos uma comparação a fim de verificar se valor do atributo Nome dos objetos do tipo Pessoa são iguais.

Com o método equals() sobrescrito ao executar novamente o código da Listagem 2 o resultado será: São a mesma pessoa, como era o esperado já que os objetos maria e maria2 possuem o mesmo nome.

Agora iremos observar como o método equals() funciona com a API Collection do Java como mostra a Listagem 4.

List<Pessoa> listaPessoas = new ArrayList<Pessoa>();
	
listaPessoas.add(maria);

if (! listaPessoas.contains(maria2)) {
  listaPessoas.add(maria2);	
}
		
System.out.println("Pessoas adicionadas: ");
		
for (Pessoa pessoa : listaPessoas) {
  System.out.println(pessoa.getNome());
}

Listagem 4 – Método contains() da API Collection do Java

Nessa Listagem, criamos uma lista de pessoas e adicionamos o objeto maria na lista. Em seguida verificamos se o objeto maria2, está na lista e caso contrário é adicionado. Ao executar o laço for será mostrado apenas um nome, ou seja, apenas o objeto maria foi adicionado na lista.

Esse comportamento se deve pelo fato do método contains() ter retornado true não adicionando assim o objeto maria2 na lista.

Isso acontece porque o método contains() da classe ArrayList faz uso do método equals(), implementando na classe Pessoa, e como ambos os objetos (maria e maria2) são “iguais” apenas um deles é adicionado na lista. Outra situação é quando trabalhando com classes da API Collections do Java que envolvem código Hash como é caso da classe HashSet, por exemplo.

Antes de prosseguirmos, com um exemplo prático, vamos discutir um pouco sobre o método hashCode().
O método hashCode() é utilizado para organizar os elementos de uma coleção em um mesmo bucket (balde ou compartimento).

Por exemplo, em uma loja as fichas dos clientes podem ser separadas em várias pastas (cada pasta seria um bucket), na pasta 1 ficariam todos os clientes cujo nome comece com a letra “A”, na pasta 2, todos os clientes que comecem com a letra “B”, e assim por diante.

Essa organização torna mais rápida a busca por um cliente, pois o logista vai até a pasta correspondente a primeira letra do nome do cliente e procura por sua ficha. Exemplo: Para um cliente de nome João, o logista pega a pasta dos clientes que começam com a letra “J” e realiza a busca por sua ficha, ao invés de procurar entre todos os cliente da loja, consequentemente aumentando a performance da busca.

Transformando essa analogia em código Java, seria: Ao analisar o nome “João” e recuperar a pasta que contém os clientes que iniciam seu nome com a letra “J”, seria a execução do método hashCode(). Ao recuperar a pasta, precisamos buscar o cliente específico, no caso, João, ao comparar seu nome com os demais para recuperar a ficha correta na pasta seria a execução do método equals().

Para gerar o código hash de um objeto é necessário sobrescrever o método hascode() da classe Object, como mostra a Listagem 5. É importante notar que esse método retorna um inteiro que representa o bucket onde se encontra o objeto.

public class Cliente {

  private Long id;
  private String nome;

  public boolean equals(Object obj) {
     Cliente c = (Cliente) obj;
     return this.id == c.getId() && this.nome.equals(c.getNome());
  }
	
  public int hashCode() {
    return this.nome.charAt(0);
  }
  /*Getter and Setter omitidos*/
}
Listagem 5 – Classe Cliente com métodos equals() e hashCode()

Nessa Listagem o método equals() irá retornar true se o id (atributo que identifica o cliente de forma única, como um CPF, por exemplo)e nome forem iguais para ambos os objetos comparados e o método hashCode() irá retornar um inteiro referente ao caracter inicial do nome do cliente definindo dessa forma o código do bucket onde o objeto cliente estará armazenado.

Vamos criar agora uma coleção HashSet para testarmos os métodos hashCode() e equals(), como mostra a Listagem 6.

Collection<Cliente> hashSetCliente = new HashSet<Cliente>();
		
Cliente cliente1 = new Cliente(1, "João");
Cliente cliente2 = new Cliente(2, "João");		
		
hashSetCliente.add(cliente1);
hashSetCliente.add(cliente2);
		
for (Cliente cliente : hashSetCliente) {
  System.out.println("ID: " + cliente.getId() + " Nome: " + cliente.getNome());
}
Listagem 6 – Adicionando clientes em um HashSet

Quando se utiliza uma estrutura de dados como HashSet, dentre outras que utilizam código hash, o método hashCode() é fundamental para seu correto funcionamento. No caso do exemplo acima os dois objetos ocuparão o mesmo bucket, já que a lógica do método hashCode() gera um código hash baseado no valor do caracter da primeira letra do nome do cliente, e no exemplo ambos começam pela letra “J”.

Uma característica da classe HashSet é que não são permitidos elementos iguais dentro da coleção.
No exemplo anterior, ambos os objetos foram incluídos na coleção porque, mesmo tendo código hash iguais, o método equals() da classe Cliente retornará false, já que o valor dos id’s são diferentes.

A saída no console após a execução deste código será:

ID: 2 Nome: João
ID: 1 Nome: João

Mas e se tentássemos adicionar na coleção o seguinte objeto: Cliente cliente3 = new Cliente(2, João)? Esse objeto não seria incluído, além de possuir o mesmo código hash do objeto cliente2 possui o mesmo nome e o mesmo id desse objeto.

Ao trabalharmos com HashSet dois objetos são considerados iguais se o método hasCode() de ambos retornar o mesmo valor e o método equals() retornar true na comparação desses dois objetos. Dessa forma apenas um objeto será incluído na coleção. Se alguma dessas afirmativas na comparação dos objetos não for verdadeira ambos os objetos são considerados diferentes.

Muito bem, depois de toda essa explicação vale o convite para praticar os conceitos apresentados para assimiliar ainda mais o aprendizado. Então, mãos a obra!

Por EDMAR BREGAGNOLI

Postado em: 15 de janeiro de 2015

Confira outros artigos do nosso blog

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

Three laws that enable agile software development

09 de março de 2017

Celso Gonçalves Junior

Medindo performance de uma API REST

21 de fevereiro de 2017

Monise Costa

Deixe seu comentário