[SCJP] dúvida hashCode() e equals

25 respostas
G

Olá, estou com uma dúvida com relação a esses dois métodos.

Se por exemplo eu usar uma classe chamada Person em uma Collection HashSet

HashSet<Person>

E sobrescrever apenas o método equals, ela não terá efeito, ou seja, poderei inserir objetos repetidos...
Observem o código

import java.util.HashSet;

public class PersonTest {
	public static void main(String[] args) {
		HashSet<Person> ps = new HashSet<Person>();
		
		ps.add(new Person("a"));
		ps.add(new Person("a"));
		ps.add(new Person("a"));		
		
		System.out.println(ps.size());
	}
}

class Person {
	private String name;
	private int age;	

	public Person(String name) {
		this.name = name;
	}

	/*public int hashCode(){
		System.out.println("h1");
		return (int)Math.random()*1000;
	}*/
	public boolean equals(Object o) {
		if (!(o instanceof Person))
			return false;
		Person p = (Person) o;
		return p.name.equals(this.name);
	}
}
O programa acima retornará 3...

Agora se eu descomentar o método hashCode(), ele retornará 1.
Não entendi como isso funciona...

Não entendi porque o método equals() só funciona quando eu sobrescrevo o método hashCode()

Alguém poderia me ajudar?

Grato.

25 Respostas

R

Imagine que vc tenha 5 baldes

|| || || || |__|

E que vc irá guardar bolas nesses baldes e depois recuperar…

Cada bola tem uma cor e um número… e o número tem relacao com a cor… Se o número da bola dividido por 5 der como resto 1… será amarelo… Se o número dividido por 5 der como resto 2 a cor será azul…

Para facilitar o seu trabalho… vc vai dividir a bola nos baldes entre cores… então todas as amarelas vc coloca no primeiro balde… todas as azuis no segundo… todas as vermelhas no terceiro… etc

Quando vc for retirar alguma bola do balde… vc irá por exemplo verificar qual é a cor… entao… se precisar retirar alguma amarela… vc vai procurar apenas no primeiro balde…

Voce nao deve guardar bolas repetidas…

Entao, ao adicionar uma bola, vc irá verificar se já existe alguma bola com o mesmo número… no balde que deseja inserir… (vc nao precisará procurar nos outros baldes pois terá certeza que aquela cor nao estará num balde diferente)

Só que vamos supor que a bola número 6 que deveria ser amarela … está com a cor azul… vc irá inserir a bola 6 azul… mesmo já existindo uma bola 6 amarela… pq vc só irá verificar o balde com as azuis… e sendo assim terá duas bolas 6… uma amarela e uma azul…

Os baldes nesse exemplo… sao como a tabela Hash… cada possiçao é um número hash

As cores sao como hash codes… cada hashcode irá para um balde diferente

E por fim o número da bola é seu valor… que vc irá compar com o equals…

Se vc criar 3 objetos… com valores iguais… ou seja… com o método equals tendo resultado como true… e nao sobrescrever o hashcode corretamente… o que vai acontecer é que vc terá 3 objetos com equals true… mas hashcodes diferentes…
O que fará com que cada objeto fique num balde diferente… resultando em valores duplicados…


No caso das bolas é como se vc tivese 3 bolas com o valor 4… mas cada uma com cor diferente…

Resultado… cada bola irá para um balde… na hora que vc for verificar uma bola se já existe… vc verificará somente em um balde… resultando em valores duplicados…


Para resolver isso… o hashcode tem que ser condizente com a implementacao de equals… ou seja… se dois objetos tiverem como equals o resultado true… os hascodes deles tem que ser iguais (para ficarem no mesmo balde)

entendeu?

V

Pelo o que eu li na documentacao, a classe HashSet utiliza a classe HashMap em sua implementacao. Isso explica esse comportamento!

A

oi gente, qual a diferença:

public static void main(String args[]){ 
		HashSet<String> lista = new HashSet<String>();
			lista.add("André");
		    lista.add("Luis");
		    lista.add("Araújo");
			lista.add("André");
		    lista.add("Luis");
		    lista.add("Araújo");
		    	System.out.println(lista);
		}
HashSet<String> lista = new HashSet<String>();

ou

Set<String> lista = new HashSet<String>();
G

Imagine que vc tenha 5 baldes

|| || || || |__|

E que vc irá guardar bolas nesses baldes e depois recuperar…

Cada bola tem uma cor e um número… e o número tem relacao com a cor… Se o número da bola dividido por 5 der como resto 1… será amarelo… Se o número dividido por 5 der como resto 2 a cor será azul…

Para facilitar o seu trabalho… vc vai dividir a bola nos baldes entre cores… então todas as amarelas vc coloca no primeiro balde… todas as azuis no segundo… todas as vermelhas no terceiro… etc

Quando vc for retirar alguma bola do balde… vc irá por exemplo verificar qual é a cor… entao… se precisar retirar alguma amarela… vc vai procurar apenas no primeiro balde…

Voce nao deve guardar bolas repetidas…

Entao, ao adicionar uma bola, vc irá verificar se já existe alguma bola com o mesmo número… no balde que deseja inserir… (vc nao precisará procurar nos outros baldes pois terá certeza que aquela cor nao estará num balde diferente)

Só que vamos supor que a bola número 6 que deveria ser amarela … está com a cor azul… vc irá inserir a bola 6 azul… mesmo já existindo uma bola 6 amarela… pq vc só irá verificar o balde com as azuis… e sendo assim terá duas bolas 6… uma amarela e uma azul…

Os baldes nesse exemplo… sao como a tabela Hash… cada possiçao é um número hash

As cores sao como hash codes… cada hashcode irá para um balde diferente

E por fim o número da bola é seu valor… que vc irá compar com o equals…

Se vc criar 3 objetos… com valores iguais… ou seja… com o método equals tendo resultado como true… e nao sobrescrever o hashcode corretamente… o que vai acontecer é que vc terá 3 objetos com equals true… mas hashcodes diferentes…
O que fará com que cada objeto fique num balde diferente… resultando em valores duplicados…


No caso das bolas é como se vc tivese 3 bolas com o valor 4… mas cada uma com cor diferente…

Resultado… cada bola irá para um balde… na hora que vc for verificar uma bola se já existe… vc verificará somente em um balde… resultando em valores duplicados…


Para resolver isso… o hashcode tem que ser condizente com a implementacao de equals… ou seja… se dois objetos tiverem como equals o resultado true… os hascodes deles tem que ser iguais (para ficarem no mesmo balde)

Ok. E…?

V

Cara, sinceramente, ja que voce esta decidido a se dedicar a java, eu recomendo que voce pegue um livro e mandar ver nos estudos. Nao me leve a mal, mas respondi a mais de um post seu e notei que essa eh uma necessidade sua!

T+

R

Voce entendeu o que eu falei?

O porque vc tem que implementar o hashcode?

Se nao seus objetos vao ficar espalhados de forma incorreta na tabela?

V

Cara…

Para vc usar qualquer lista que implemente a interface Set, necessintam que vc implemente equals e hashCode, justamente porque elas como o nome diz ela, usa codigo hashing.

Por isso não funcionou…

E ira retornar sempre 1, pq Math.random sempre retorna um numero entre 0, 0.5, acredito que esse seu metodo hashCode não ta tão lega, recomento que de uma lida na

documentação para buscar melhores referências…

R

vitorfarias:
Cara…

Para vc usar qualquer lista que implemente a interface Set, necessintam que vc implemente equals e hashCode, justamente porque elas como o nome diz ela, usa codigo hashing.

Por isso não funcionou…

E ira retornar sempre 1, pq Math.random sempre retorna um numero entre 0, 0.5, acredito que esse seu metodo hashCode não ta tão lega, recomento que de uma lida na

documentação para buscar melhores referências…

Alguns ajustes técnicos apenas…

Para você usar qualquer coleção que implemente a interface Set (se nao pode confundir com a interface list…)

Melhorando um pouco esse raciocínio… Se você sobrescreveu o equals obrigatoriamente tem que sobrescrever o hashcode (e sobrescrever de acordo com as regras do método equals e hashCode…)

Irá retornar sempre 0… e Math.random sempre retorna um número entre 0 e 1 (nao incluindo o 1)

R
andredecotia:
oi gente, qual a diferença:
HashSet<String> lista = new HashSet<String>();

ou

Set<String> lista = new HashSet<String>();

Em um a sua variavel lista é do tipo HashSet.. no outro é do tipo Set :lol:

G

Voce entendeu o que eu falei?

O porque vc tem que implementar o hashcode?

Se nao seus objetos vao ficar espalhados de forma incorreta na tabela?

Acho que não compreendeu corretamente a pergunta…
Quero saber qual método válida se os dois métodos foram sobrescritos (ou seja lá como isso é validado)…

G

Fala andredecotia, blz?
Ow essa pergunta é sobre polimorfismo, não tem muita ver o tópico.
Rola mover para outro tópico?

Abs.

R

gervas-IO:
Voce entendeu o que eu falei?

O porque vc tem que implementar o hashcode?

Se nao seus objetos vao ficar espalhados de forma incorreta na tabela?

Acho que não compreendeu corretamente a pergunta…
Quero saber qual método válida se os dois métodos foram sobrescritos (ou seja lá como isso é validado)…

Eu entendi o seu problema… voce nao compreendeu o que eu falei

O equals e o hascode trabalham juntos… nao é um ou outro… são os dois

G

Ok jovem, eu compreendi o que você falou, tb agradeço sua prestatividade!

Estou tentado entender como ocorre essa chamada

O HashSet vai e chama o hashCode de um objeto. Ok.
Se caso eu sobrescreva somente o método equals() e não o hashCode(), o equals() não vai ser chamado… mas porquê???

Se lá por meio do código ocorre a chamada:
a.equals(b)

a.equals() => terá que chamar o método sobrescrito… mas não chama!!!

Não quero saber sobre a funcionalidade da inserção em tabelas Hash, somente isso que eu quero saber…


O equals e o hascode trabalham juntos… nao é um ou outro… são os dois

Como isso ocorre???

Mas cara, se vc não entendeu deixa pra lá, blz?

R

hehehehe… entendi sim… afinal já tive a mesma dúvida algum dia…

O caso é o seguinte… se vc sobrescrever o método equals e nao o hascode … o equals será chamado…

Só que o equals só será chamado nos objetos que tiverem o mesmo hashcode (na analogia da bola… o equals só será chamado para as bolas da mesma cor)

Sacou??

Primeiro é feito o hashcode do objeto… e entao o equals só será chamado pra quem tiver o mesmo hashcode…

Por isso que… se a.equals(b) for true… a.hasCode() == b.hashCode()

No seu caso vc acha que o equals nao tá sendo chamado… pq ele deixou duplicados num foi isso? Na verdade o equals foi chamado sim… mas apenas nos objetos com mesmo hashcode… como cada objeto seu tinha um hashcode diferente… o HashSet já considerou que eram objetos diferentes…

Isso é para ter performance…

O hashcode é muito mais barato que um equals…

Entao vc faz um hash e depois compara com equals apenas os objetos com o mesmo hash…

Esclareceu?

R

O problema é que vc achou que o algoritmo era diferente se vc sobrescrever o equals e/ou o hashCode…

Mas o algoritmo do hash nao muda…

Só que ele fica falho se vc nao seguir o protocolo…

G

Aí é que está cerne da questão:

No código que postei insira a linha System.out.println(“equals”); no método equals, e verá que ela não é executada… Tudo isso que você disse faz muito sentido, porém pra mim se ele não executa a linha não faz sentido que esteja chamando o método…

Sacou?

R

Pois entao… o equals só será chamado para objetos com hashcode iguais… como tinha dito

Se o hashcode for diferente… o HashSet nao se dará ao trabalho de chamar o equals…

G

Caraca…
Entendi!

Vlw!

R

hehehe… blza

flw

V

Uma implementação possível do hashSet é um array de listas.

Imagine que cada índice do array são as gavetas de cores, que nosso colega falou. E as listas, é o espaço da gaveta, onde você pode colocar as meias.

Pois bem, se você não implementar o hashCode, o java utilizará a implementação padrão. Essa implementação usa o endereço de memória do objeto para dizer seu hash. Ou seja, dois objetos, usando esse algoritmo, jamais terão o mesmo hashCode. Portanto, é como se todas as suas meias tivessem cores diferentes. Essa é a mesma política adotada para o equals, portanto, esses métodos estão consistentes, mesmo com sua implementação padrão. Ou seja, para a implementação padrão, com objetos diferentes, você nunca precisará testar se uma meia é igual a outra, pois, nessa implementação, você tem exatamente uma gaveta por meia. Você pode fazer com que o método equals seja chamado, basta inserir 2 vezes o mesmo objeto no set.

Quando você implementa o equals, você está alterando a definição de igualdade. Você está dizendo que dois objetos de endereço de memória diferentes podem, eventualmente, ser considerados iguais. Portanto, é necessário também fornecer um hash que indique que eles sejam iguais. O hash é um número, e serve como um atalho para definir se dois objetos são ou não iguais.

Entretanto, o contrário não é verdadeiro. Dois objetos de mesmo hash podem, eventualmente, não serem iguais. O hash é um atalho, o equals é o método que realmente distingue um objeto de outro. Como um HashSet não permite duplicatas, como ele fará para não descartar objetos de mesmo hash que sejam diferentes? É aí que entra a regra que o colega tentou te explicar. Sempre que ele encontra um hash igual, ele irá comparar se o objeto encontrado lá é mesmo igual ao objeto recebido. Se for igual, o objeto será substituido pelo passado. Se for diferente, o objeto será colocado na mesma posição do hashmap, no próximo índice livre da lista.

Um exemplo de caso de hashs iguais são as Strings “auréola” e “desprezível”, como bem citado pelo Thingol. Obseve que a mesma estratégia de combinar hash com equals é também usada no switch do Java 7, como ele explica nesse post:

Z

ViniGodoy:
Uma implementação possível do hashSet é um array de listas.

Imagine que cada índice do array são as gavetas de cores, que nosso colega falou. E as listas, é o espaço da gaveta, onde você pode colocar as meias.

Pois bem, se você não implementar o hashCode, o java utilizará a implementação padrão. Essa implementação usa o endereço de memória do objeto para dizer seu hash. Ou seja, dois objetos, usando esse algoritmo, jamais terão o mesmo hashCode. Portanto, é como se todas as suas meias tivessem cores diferentes. Essa é a mesma política adotada para o equals, portanto, esses métodos estão consistentes, mesmo com sua implementação padrão. Ou seja, para a implementação padrão, com objetos diferentes, você nunca precisará testar se uma meia é igual a outra, pois, nessa implementação, você tem exatamente uma gaveta por meia. Você pode fazer com que o método equals seja chamado, basta inserir 2 vezes o mesmo objeto no set.

Quando você implementa o equals, você está alterando a definição de igualdade. Você está dizendo que dois objetos de endereço de memória diferentes podem, eventualmente, ser considerados iguais. Portanto, é necessário também fornecer um hash que indique que eles sejam iguais. O hash é um número, e serve como um atalho para definir se dois objetos são ou não iguais.

Entretanto, o contrário não é verdadeiro. Dois objetos de mesmo hash podem, eventualmente, não serem iguais. O hash é um atalho, o equals é o método que realmente distingue um objeto de outro. Como um HashSet não permite duplicatas, como ele fará para não descartar objetos de mesmo hash que sejam diferentes? É aí que entra a regra que o colega tentou te explicar. Sempre que ele encontra um hash igual, ele irá comparar se o objeto encontrado lá é mesmo igual ao objeto recebido. Se for igual, o objeto será substituido pelo passado. Se for diferente, o objeto será colocado na mesma posição do hashmap, no próximo índice livre da lista.

Um exemplo de caso de hashs iguais são as Strings “auréola” e “desprezível”, como bem citado pelo Thingol. Obseve que a mesma estratégia de combinar hash com equals é também usada no switch do Java 7, como ele explica nesse post:
http://thingol-guj.blogspot.com/2010/03/switch-com-strings-no-java-7.html

Valeu Vini .

Re-lembrei toda essa parte de HashCode e Equals com a sua resposta . abs

G

Ótima discussão, estava com dificuldades para entender o funcionamento dos métodos equals/hashcode, agora ficou claro, vlw galera :slight_smile:

R

rogelGarcia, cara parabéns pela explicação, estou estudando para certificação, e estava aqui a procurar uma explicação para uma questão do mock, e vi sua ultima explicação show de bola!

J

ficou assim.

public boolean equals(Object obj) {  
    if (this.clube == obj) {  
        return true;  
    }else{
        return false;
    }
}
public class Teste{
      
        
      public static void main(String[] args) {  
        HashSet<Partida> partidas = new HashSet<Partida>();  
          
        partidas.add(new Partida("Palmeiras", "Santos" , "Palestra" , 2, 1)); 
         partidas.add(new Partida("Palmeiras", "Santos" , "Palestra" , 2, 1));  
                  
  
        System.out.println(partidas.equals(partidas));  
    }  
}

e como eu faço a outra parte?

R

Sua pergunta está incompleta, como eu faço a outra parte???

Primeiro seu equals está “estranho”, esta considerando o mesmo endereço de memória do objeto clube, acredito que o equals já está meio estranho, pois deveria considerar o “nome” do clube, depois você está usando o objeto Partida dentro da coleção…

Tente definir melhor o que quer.

Criado 16 de julho de 2010
Ultima resposta 16 de jun. de 2013
Respostas 25
Participantes 10