É, 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
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().