31 agosto 2009

SCJP - 07 - Equals e Hashcode

É, eu sei, eu tenho estudado pouco para a certificação. E sei que isso mais cedo ou mais tarde vai dar merda... Comecei o capítulo 7 hoje, e nem sei quando eu vou terminar de lê-lo. Ando só lendo, nada de exercício, trabalho nao deixa, e chego meio cansado em casa... Bem, chega de mimimi e vamos ao que interessa.


Equals - Pra que?


Por que usar equals? Quando você não o sobrescreve, acaba usando a implementação da classe Object, onde equals() e == significam a mesma coisa. O operador == verifica se dois objetos são idênticos, ou seja, se duas variáveis de referência possuem a mesma sequência de bits, o que significa que referenciam o mesmo objeto em memória.

Um bom motivo para usar uma versão sobrescrita do método equals() é a necessidade de usar um Map no Java. Map é uma estrutura na qual você mapeia um valor do tipo V usando uma chave do tipo K. As chaves devem ser únicas no mapa. Você, invariavelmente, vai precisar instanciar um novo objeto do tipo K, usando certos dados significativos, para resgatar um valor do tipo V no Map. Se não implementar o método equals(), o objeto que foi usado como chave no Map nunca será igual ao que você acaba de instanciar.

Equals - Como?


Implementar o método equals() é relativamente simples, mas se faz necessário certos cuidados. Quando se usa a geração automática de código do Eclipse, algo parecido com o código abaixo é gerado:

public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (!super.equals(obj))
        return false;
    if (getClass() != obj.getClass())
        return false;

    ...
}

Primeiramente, se duas variáveis de referência referenciam o mesmo objeto, obviamente, elas possuem o mesmo conteúdo. Se a sua classe é derivada de outra, é prudente fazer um teste com o equals() da superclasse. Note também que a terceira condicional tem semântica semelhante ao instanceof.

Às reticências, seguiriam um cast para o tipo da classe, e a comparação, campo a campo, da chamada chave de negócio.

Chave de negócio, entre outras palavras, é um conjunto de campos que tornam um registro único em relação aos demais.

Para uma implementação "correta" do método equals, note o seguinte:

  • O método equals() é público, precisa receber um Object como parâmetro, e deve retornar um valor primitivo boolean.
  • Reflexão: Para todo objeto x, o teste x.equals(x) é verdadeiro.
  • Elemento Nulo: Para todo x, x.equals(null) é falso.
  • Transitividade: Para todo x, y, z, se x.equals(y) e y.equals(z), então x.equals(z).
  • Simetria: Para todo x e y, se x.equals(y), então y.equals(x).
  • Consistência: Obviamente, que para todo x e y, se nenhuma informação usada na comparação em ambas as variáveis de referência for modificada, múltiplas chamadas a x.equals(y) ou retornam true, ou retornam false.

Hashcode - Pra que?

Uma função hash é um procedimento bem definido ou função matemática que converte uma quantidade variável (possivelmente grande) de dados em um pequeno dado, muitas vezes um inteiro, usado como índice em tabelas. [Wikipedia]
As tais tabelas, citadas na definição acima traduzida livremente por mim, fazem referênia às tabelas de dispersão:
Em ciência da computação, uma tabela de dispersão (também conhecida por tabela de espalhamento ou tabela hash, do inglês hash) é uma estrutura de dados especial, que associa chaves de pesquisa a valores. Seu objetivo é, a partir de uma chave simples, fazer uma busca rápida e obter o valor desejado. [Wikipedia]
Em java, estruturas como HashMap, HashSet e outras, usam o hashcode para definir onde um registro será armazenado, de forma que, também usando o hashcode, este registro seja resgatado com custo O(1) no melhor caso (complexidade de algoritmos). Para se resgatar um registro, usa-se normalmente o hashcode() para saber em quais posições da estrutura de dados procurar, e o método equals() para verificar se aquele é o registro procurado. Por isso, costuma-se dizer que, para manter a lógica da aplicação, equals() e hashcode() devem usar a mesma chave de negócio. Além disso, se dois objetos podem cair numa mesma posição da estrutura de dados, usando o hashcode() como meio de endereçamento, isso significa que existem x e y para os quais hashcode(x) = hashcode(y), o que costuma-se chamar de colisão.

Hashcode - Como?

Existem muitas formas de implementar uma funçao hashcode, como também descrito aqui, mas vamos dar uma olhada naquela que o Eclipse nos fornece, automaticamente:
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result
        + ((campoX == null) ? 0 : campoX.hashCode());

    ... // para outros campos, siga como acima.
    return result;
}
É possível perceber que, ao final, é feita uma multiplicação como a seguinte:
hash(A1,...,An) = n * hash(A1,...,An-1) + hash(An)
Onde n é um número primo.
Isso porque a escolha de n influencia o espalhamento dos registros, e números primos tendem a distribuir os restos da divisão de maneira mais uniforme que os não primos. [Hashing. Pimente, Graça & Cristina, Maria. ICMC]
Obs.: Não tomem essa função como 100% correta :P Assim como a implementação do equals() segue um certo contrato, também o é com a implementação do hashcode().
  • Sempre que invocado no mesmo objeto mais de uma vez durante a execução de uma aplicação Java, o método hashcode() precisa retornar o mesmo resultado, se nenhuma informação usada pelo método equals() for alterada. O método hashcode() não precisa manter a consistência do resultado de uma execução da aplicação para outra.
  • Para todo x,y, se x.equals(y) == true, então hashcode(x) == hashcode(y). Pela lei da contraposição, para os mesmos x,y, se hashcode(x) != hashcode(y), então x.equals(y) == false.
  • Não é necessário que dois objetos com o mesmo hashcode() sejam iguais, pelo método equals().
Acho que falei demais, né? :P Espero que tenha ficado o mais claro e resumido possível. Aparentemente, quando se pensa em equals() e hashcode(), não se imagina quanta coisinha de lógica e regras tem nos bastidores... E, pra variar, no caso de usar campos de data no hashcode, sugiro que você dê uma olhada nisso.
Creative Commons License Esta obra está licenciada sob uma Licença Creative Commons.
Comentários
1 Comentários

1 comments:

Wendel disse...

Meu colega muito legau o sue post vc e um camarada que sabe o que faz! Obrigado!

Postar um comentário

Regras são chatas, mas...

- Seu comentário precisa ter relação com o assunto do post;
- Em hipótese alguma faça propaganda de outros blogs ou sites;
- Não inclua links desnecessários no conteúdo do seu comentário;
- Se quiser deixar sua URL, comente usando a opção OpenID;
- CAIXA ALTA, miguxês ou erros de ortografia não serão tolerados;
- Ofensas pessoais, ameaças e xingamentos não são permitidos;