ToString, Equals e HashCode
Autor: Gustavo | Publicado: 2015-03-20T12:24:00-03:00




 


 Falarei
aqui sobre uma das boas páticas de programação e muito tem a ver
com três métodos muito importantes e que devem ser sempre que
possível sobrescritos em uma classe. Além de constar em boa parte
do exame OCJP que é o meu foco principal deste blog são os métodos
toString(), equals()
e hashCode().

 Todos
estes
métodos são
muito importantes na construção de um algorítimos que poderá
no
seu código informar
algo sobre
o objeto,
facilitar
o procesamento na pesquisa
e/ou
realizar uma comparação
entre um objeto e outro. Isto
porque em java, tudo é objeto  tirando
os tipos primitivos e
sabemos
que um
objeto
por sua vez possui um estado, um comportamento e
aí por diante.

 




Sobrescrevendo
o método toString()




 Sabemos
que todos os objetos instanciados queira ou não, extendem a classe
Object.
Isso
significa que todo e qualquer objeto que você estiver trabalhando
irá possuir o método toString() mas isso tem seus problemas. Em
muitos dos casos este método não nos traz nenhuma informação
muito importante.

 Por
exemplo, se pegarmos uma classe como abaixo e
em um método main, chamarmos o método toString()
teremos o seguinte resultado:




public
class
Surfista {




 public
static
void
main(String args[]){

  Surfista
s
= new
Surfista("Miguel
Pupo",
"Dropador",
21,1);

  System.out.println(s.toString());

  

 }




 String
nome;

 String
apelido;

 int
idade;

 int
id;




 public
Surfista(String nome,
String apelido,
int
idade,int
id)
{

  setId(id);

  setApelido(apelido);

  setIdade(idade);

  setNome(nome);

 }




...getters
&amp; setters comuns abaixo 




Saida:




br.com.blog.gmachado1.model.Surfista@11c491




Agora
a pergunta, isso acrescrenta informação do objeto? Ou melhor,
informa algo?Pra
mim não! 


 Então
sempre que possível podemos sobrescrever o método toString na
classe colocando um método com as mesmas assintaturas (nome do
método, retorno e parâmentro) fazendo assim trazer alguma
informação mais útil. Que tal esta?




 @Override

 public
String toString() {

  return
"Surfista
[nome="
+ nome
+ ",
idade="
+ idade
+ "]";

 }




Saida:




Surfista
[nome=Miguel Pupo, idade=21]




Agora
podemos tirar mais informações sobre este objeto concorda? 




Sobrescrevendo
o método equals()




 Exceto
em alguns momentos onde podemos comparar duas variáveis primitivas,
a maioria dos casos em que necessitarmos realizar a comparação
sempre iremos usar o método equals() que é um
dos  vários métodos herdados da classe Object e que é aconselhável
ser sobrescrito. 


 A
utilização para comparar dois objetos utilizando o operador igual
(= =)  que é o que ocorre na implementação da classe object, pode
até acertar em alguns casos mas isto não poderá te oferecer
segurança pois a sua forma de comparação é relativa aos objetos
onde serão considerados
iguais se somente se
as duas referências compartilharem o mesmo objeto, o que poucas
vezes isso ocorre além de
sabermos que apesar de serem objetos diferentes, eles podem estar se
referindo ao mesmo.

 Vamos
seguir o  código dado até aqui e imaginar o código da seguinte
forma:




public
class
Surfista {

 int
id;

 String
nome;

 String
apelido;

 int
idade;




 public
static
void
main(String args[]){

  Surfista
s1
= new
Surfista("Gabriel
Medina",
"Medina",
19, 1);

  Surfista
s
= new
Surfista("Gabriel
Medina",
"Medina",
19, 1);







  System.out.println(s.equals(s1));

  

 }

 //getters
e setters abaixo

 

 Saida:

 false

 

 Supondo
que id seja um valor único na tabela que defina a identidade do
objeto.

Ao
definirmos 2 surfistas iguais, ao verificarmos sua igualdade pela
forma tradicioal não teremos sucesso conforme pode ver acima, apesar
de sabermos que são os mesmos.

Isto
ocorre por termos 2 instâncias iguais em memórias diferentes, para
resolvermos este problema, podemos simplesmente sobrescrever o método
equals da melhor forma que convém.

No
nosso caso:




 @Override

 public
boolean
equals(Object obj)
{

  if(obj
instanceof
Surfista &amp;&amp; (((Surfista)obj).getId()
== this.id)){

   return
true;

  }else{

   return
false;

  }

 }




 Primeiro
passamos o parâmentro como object até porque deve sobrescrever
então devemos passar sempre o mesmo tipo de parâmentro.

Segundo
devemos verificar se é uma instância da classe é
a esperada, para que possamos
em seguida converte-la sem
ocorrer ClassCastException, para em
seguida obter
seu(s) valores chaves e
realizar a comparação
entre os valores do objeto atual e o passado por parâmetro.

Ao
sobrescrevermos o método equals da nossa classe podemos rodar e
verificar o que ocorre...

 Para
sobrescrever um método equals, devemos sempre nos preocupar com
alguns “contratos” que devem ser seguidos tais como:


 Reflexivo:
 Para qualquer valor de referência de x, x.equals() deve retornat
 true.


 Simétrico:
 Para qualquer valor de referência de x e y. se x.equals(y) retornar
 true, y.equals(x)
 deverá também retornar true.


 Transitivo:Para
 qualquer valor de referência x, y e z, se x.equals(y) retornar true
 e y.equals(z) retornar true. Necessáriamente z.equals(x) terá que
 retornar true;


 Consistente:
 Não importa o número de vezes que irá realizar a igualdade entre
 2 objetos, se nada foi alterado, sempre que uma validação de
 igualdade der true deverá ser true ou caso de false deverá ser
 false.






Sobrescrevendo
o método hashCode()




 Os
métodos hashCode() são utizados para facilitar as buscas de um
objeto. Alguns conjuntos de objetos tais como HashMap e HashSet entre
outros se utilizam do algorítmo deste método que retorna sempre um
número inteiro para auxiliar nas buscas por um item da lista, desta
forma ao invés de realizar a busca  por um item em toda lista,
poderá ser feito uma busca encadeada que trará maior performance na
sua busca.

 Comecarei
falando sobre as vantagens de se encadear uma lista no nosso projeto,
as observações que deverão ser feitas ao se utilizar desta
funcionalidade, como implementar de forma válida e eficiente e os
contratos que deverão ser seguidos. Desta forma seu código ao deste
post poderá retornar uma pesquisa com muito mais eficiência do que
anterioremente.




Vantagens
de uma lista ecadeada




 O
maior benefício de uma busca encadeada é que a otimização do
tempo de uma busca e de processamento que deve diminuir de acordo com
o número de registros. Por exemplo, se possuimos uma lista encadeada
para guarda objetos que representem surfistas do campeonato WCT que
possui 1.000 participantes, em média, seus sistema terá que
percorrer 500 posições (uma para o melhor caso e mill para o pior
caso) ao tentar buscar um surfista certo?

 Querendo
melhorar nossas buscas em uma aplicação, criamos um array de 26
posições, cada um representando uma letra do alfabeto. E em cada
posição deste array tivesse
uma lista encadeada contendo somente surfistas
cujo nome se inicia com aquela dada letra. Nessa
estrutura percebemos
que podemos
diminuir
o tempo
de busca, já que você dividiu os surfistas
em grupos.

Beleza!
Acabamos
de implementar no
método hashcode otimozando
a
busca,
apesar
de ser um pouco
problemática ainda. Principalmente porque está desbalanceada. 


 Digo
isto porque provavelmente na hash de
letra “A”,
estaria concentrado boa parte dos surfistas
e na hash de
letra “W”,
quase nenhum. Percebe isso?

Outro
problema da nossa
hash
é que limita a sua estrutura a 26 hashs enquanto o ideal é que o
número de hashs possa crescer de acordo com o volume de dados.
Observe que neste exemplo, sua implementação de hashCode seria a
conversão da 1a letra do nome em um valor entre 0 e 25 (26
possibilidades de colisão). Esse hashCode serve justamente para
sabermos em que hash um objeto (surfista)
entraria.

 Vamos
então melhorar
nosso algorítmo do hashCode?
Imagine agora um array de 500 posições abrigando em suas posições
as listas encadeadas. Imagine tb que seu hashCode faça a seguinte
conta:


 Pegue
 cada letra do nome de um surfista,
 converta no valor ascii e some.


 Pegue
 o
 total
 e dividiremos por 500 e pegaremos o resto da divisão.



Bem,
notadamente, numa divisão por 500, só podemos ter resto de 0 até
499. Perceba que é exatamente a variação de seu array com 500
posições!!!

Logo
ao armazenarmos
nossa lista nesta
nova estrutura teremos uma média de apenas 2 surfistas
por
hash! Conseguimos melhorar o
código
inicial que percorria em média 500 em cada busca para 2! Neste caso
o load factor desta estrutura que você criou é 2 (uma média de 2
elementos por hash)




Observações
em um Hashcode




 Sempre
que for sobrescrever o método hashCode, deverá ter algumas atenções
fáceis de serem lembradas pois são muito parecidas com as do método
equals() caso consulte a API do java verá as seguintes
observações como contrato de um hashCode. 



 Sempre
 que for chamado no mesmo objeto mais de uma vez durante a execução
 de um aplivativo Java, o método hashCode() tera que retornar
 consistemtemente o mesmo inteiro. Contato que nenhuma informação
 usada nas comparações equals() envolvendo o objeto tenha
 sido alterada. Esse inteiro terá que permanecer constante de uma
 execução a outra do mesmo aplicativo.


 Se
 dois objetos forem iguais de acordo com o método equals(Object),
 então, a chamada de método hashCode() nos dois objetos deve
 produzir como resultado o mesmo inteiro.


 Não
 é obrigatório que quando dois objetos forem diferentes de acordo
 com o metodo equals(Object), a chamada ao método hashCode()
 nesses objetos produza os mesmos resultados inteiros distintos.
 No entanto, o programador deve ficar alerta para oo fato de que
 produzir resultados inteiros distintos para objetos diferentes pode
 melhorar o desempenho das tabelas hashing.






 Em
suma o que é dito aqui é que, ao elaborarmos um código para um
método hashCode(), ele deverá ter não somente os contratos
de um método equals() (reflexivo, simetrico, transitivo e
consistente) mas será desuma importância o programador conhecer a
lógica e o ambiente em que será utilizado sua aplicação para
melhor saber criar os hashs de memória para melhor divisão dos 
objetos afim de serem encontrados com maior eficiência.

 Um
gerador de código hashing que retorne sempre o mesmo valor por
exemplo seria válido mas não ajudaria muito porque independente do
nome dos surfistas, todos os surfistas do campeonato seriam
armazendos no mesmo array o que não mudaria na otimização de tempo
e processamento de busca.

 Outro
erro comum de ocorrer é utilizar no método hash variáveis locais
transientes como abaixo:




public
class
Surfista {




 String
nome;

 String
apelido;

 int
idade;

 int
id;

 transient
int
result;




 @Override

 public
int
hashCode() { //imagine
alguma
lógica
aqui




  

 Isto
porque como já sabemos uma variável transiente não é carregada em
muitas das vezees por uma base de dados. É uma memória que poderá
ser nula em alguns momentos fazendo com que gere erro durante a
elaboração da lógica do algorítimo. Não será gerado erro de
compilação mas muito possivelmente poderá ocorrer erro durante a
execução da aplicação. Isso sem contar que a serialização salva
objetos de maneira que ele possa ser reativado posteriormente pela
sua desserialização, voltando a ser um objeto totalmente válido,
mas as variáveis de ambientes transientes não são salvas quando um
objeto é serializado.




Implementando
uma forma válida e eficiente




 Para
finalizar irei apenas demonstrar como sobrescrever o método
hashCode()
na
prática.
Neste caso pegamos cada
atributo
da classe, somamos
com nosso variável result e multiplicamos por um numero primo (que
sempre será o mesmo apesar de ser escolhido
aleatoriamente).
Desta
forma geramos
um número
para
has.
Nos
atributos
texto, fazemos com que meça
o
tamanho do
texto e divida por dois
para
depois
seguir
aos mesmos procedimentos anteriores,
somando
com a
variável result e
multimplicando
pelo numero primo novamente.




public
class
Surfista {




 String
nome;

 String
apelido;

 int
idade;

 int
id;




 @Override

 public
int
hashCode() {

  final
int
prime
= 31;

  int
result
= 1;

  result
= prime
* result

    +
((apelido
== null)
? 0 : apelido.trim().length()/2);

  result
= prime
* result
+ id;

  result
= prime
* result
+ idade;

  result
= prime
* result



     +
((nome
== null)
? 0:nome.trim().length()/2);

  return
result;

 }

  @Override

  public
boolean
equals(Object obj)
{

 if
(obj
instanceof
Surfista &amp;&amp; (((Surfista) obj).getId()
== this.id))
{

   return
true;

  }
else
{

   return
false;

  }

 }




}