Busca em HashMaps[Resolvido]

Olá pessoal. Eu estou estudando para a certificação pelo livro da Kathy Sierra. Estou no capítulo sobre Collections e Genéricos. Cheguei na parte em que é explicado o funcionamento da interface Map, mas não consegui entender direito o seu funcionamento. Me deu um nó na cabeça. Bom, vou postar o pedaço do código da minha dúvida e tentar explicar o que eu não entendi.
Primeiramente há uma classe Dog que implementa de forma correta os métodos equals() e hashCode():

import java.util.*;

class Dog{

   public Dog(String n) { name = n; }
   public String name;

   public boolean equals(Object o){

      if((o instanceof Dog) && ((Dog)o).name == name){
          return true;
      }  else{
           return false;
      }

   }

   public int hashCode() { return name.length(); }

}

Depois a classe que usa o Map:

class MapTest{

   public static void main(String[] args){
      Map<Object, Object> m = new HashMap<Object, Object>();

      Dog d1 = new Dog("clover");
      m.put(d1, "Dog Key");

      System.out.println(m.get(d1));
     
      d1.name = "magnolia";
     System.out.println(m.get(d1));

     d1.name = "clover";
     System.out.println(m.get(new Dog("clover")));

     d1.name="arthur";
     System.out.println(m.get(new Dog("clover")));    
  
   }
}

A saída desse código é: Dog Key, null, Dog Key, null.
Ok, vamos lá. A primeira saída é fácil de entender. Afinal, foi criada uma chave que é um objeto Dog para o valor String “Dog Key”. Minha dúvida começa a partir do segundo get. O código muda o nome do Dog de “clover” para “magnolia” e usa o próprio objeto para buscar o valor “Dog Key”. Mesmo que o nome tenha mudado, e consequentemente o código hash da chave também, se o próprio objeto é a chave, por que ele não conseguiu achar “Dog Key”? Pois a chave é o objeto referenciado por d1 e o método get é chamado usando-se o d1. Com isso eu imaginei: "Bom, ele deve ter mantido o código hashing de quando o valor foi inserido junto com a chave na primeira chamada a put() e essa chave é 6, diferente de ‘magnolia’ que é 8’ ". Mas se fosse dessa maneira que pensei, para mim o último get deveria retornar o valor correto “Dog Key” e não null. Pois como estou passando um objeto com o nome “clover” e na hora da inserção chave/valor, o objeto era “clover”, ele deveria retornar o valor correto. Mas não é desse jeito. Ele passou um objeto Dog novo e o comparou com o objeto dog que é a chave, o qual é referenciado por d1. Como possuem valores diferentes para nome, não passam no teste do equals(). Mas por que no caso de “magnolia” não deu certo se a própria chave é o d1? Não sei se fui muito compreensivo na dúvida, mas…
Agradeço a ajuda e desculpem pelo longo texto.

Abraços!

[quote=douglas_vidotto]

[code]
if((o instanceof Dog) && ((Dog)o).name == name){

[/code][/quote]
A comparação de Strings não deve ser feita com ==

A observação do pmlm está correta, mas acho que não esclarece sua dúvida. Supondo que você implemente o equals corretamente assim:

Não sei se entendi direito a sua dúvida, mas o que ocorre é o seguinte.

Dog d1 = new  Dog("clover");  
m.put(d1, "Dog Key");

Aqui o hash de d1 é calculado. Como d1 é na verdade um Dog, o Java vai usar a implementação hash e equals da sua classe Dog. Ao dar o put acima, o hash é calculado. Pela implementação da sua classe Dog o hash acaba usando simplesmente a implementação hash da classe String. Assim, o hash do atributo “name” é calculado. Vamos supor (só para ficar mais fácil) que esse hash tem 10 “recipientes” e que, ao calcular esse primeiro hash (“clover”) tal hash dê 2. Logo, d1 será colocado no “recipiente” 2.

d1.name = "magnolia"; System.out.println(m.get(d1));

Aqui vc mudou o atributo do objeto d1 para “magnolia”, porém, d1 continua no “recipiente” 2 !!! Ao fazer m.get(d1), o hash é calculado, mas dessa vez, é calculado com relação à String “magnolia”. Vamos supor que tal hash dê 6. Assim, o Java vai sair percorrendo o “recipiente” 6 e comparando um a um com o método equals até ver se acha algum objeto igual (esse igual é sempre com relação ao método equals) ao passado no parêmetro get. Porém, não há nenhum objeto no “recipiente” 6! (Apenas no 2). Logo, ele não acha nada e retorna null.

d1.name = "clover";  
System.out.println(m.get(new Dog("clover")));

Aqui vc mudou o parâmetro novamente para “clover”. O objeto d1 continua no “recipiente” 2. Na chamada do get, ele vai usar o hashcode novamente, mas dessa vez o hashcode vai dar 2 também (pois é a mesma String e o hashcode implementado por você apenas usa essa String). Assim, o Java vai sair comparando tudo que estiver no “recipiente” 2 com o método equals. Agora sim, como d1 está ali no recipiente 2 (e d1.name possui a String “clover”), ele vai encontrá-lo e, logo, retornar o valor correspondente (no caso, “Dog key”).

d1.name="arthur";  
System.out.println(m.get(new Dog("clover")));

Aqui vai acontecer a mesma coisa do passo anterior. Porém, na hora do Java comparar com o método equals no “recipiente” 2, ele não vai encontrar nada (pois o atrivuto “name” de d1 não é mais “clover” e sim “arthur”). Logo a comparação com equals falha e retorna null (não achou nada).

Acho que era essa a sua dúvida. Se não for, poste novamente que eu tento ajuda de outra forma. :smiley:

Fala Tiago…blz? Minha dúvida era exatamente essa e você conseguiu esclarecer muito bem!! Muito obrigado. Quanto ao código em que ele faz a comparação de Strings com ==, realmente está inadequado. Mas eu copiei exatamente como estava no livro (tirando só os pedaços que não faziam parte da dúvida). Mas confesso que nem tinha prestado atenção nisso, hehehehe.