[Duvida] HashCode() do Set

13 respostas
C

Por que set.size() sempre retorna “1”?

package CollectionsGenerics.medio;

import java.util.HashSet;
import java.util.Set;

public class Questao05 {

	public static void main(String ... args) {
		Set<Prova> set = new HashSet<Prova>();
		set.add(new Prova("SCJP"));
		set.add(new Prova("SCJD"));
		set.add(new Prova("SCJP"));
		System.out.println(set.size());
	}  
}

class Prova{
	String nome;
	Prova(String nome) {this.nome = nome;}
	public int hashCode() {return 0;}
	public boolean equals(Object o) {return true;}
}

13 Respostas

T

Você definiu equals de modo que dois objetos diferentes retornam “true” para equals. Dessa forma, o “add” sempre acha que você está tentando inserir o mesmo objeto, e o tamanho do hashset acaba ficando igual a 1.

Andre_Fonseca

Como você sobrescreveu o equals para sempre retornar true ele só vai inserir uma vez certo??

J

Devido a implementação do método equals sempre retornar true todos os objetos inseridos serão considerados iguais e um Set não aceita duplicatas, portanto será inserido o primeiro objeto e os demais serão descartados. Segue uma maneira de implementar equals:

public boolean equals(Object o){ if((o instanceof Prova)&&(((Prova)o).nome.equals(this.nome))){ return true; } return false; }
Aqui os objetos Prova serão comparados pelos seus nomes para definir a igualdade. A primeira coisa a ser feita é uma verificação se o objeto no qual a referência passada ao método equals é realmente uma instância de Prova se não for o teste é encerrado e na execução da próxima instrução retorna false (significa que são diferentes), se for o teste segue verificando se os objetos Prova possuem o mesmo nome, caso afirmativo ele retorna true senão encerra a execução do teste e novamente executando a próxima instrução retorna false.

Daniel_Reis

Os dois métodos estão sobrescritos de forma errada, o correto seria:

@Override
	public int hashCode() {
		return nome.hashCode();
	}

	public boolean equals(Object o) {
		if ((o instanceof Prova) && (((Prova) o).nome.equals(this.nome))) {
			return true;
		}
		return false;
	}
J

Forma errada acredito ser um termo um tanto quanto exagerado. Eu diria ineficiente, principalmente em se tratando de certificação.

T

Uma implementação adequada de hashSet e equals é tediosa e não é trivial.

Por exemplo, normalmente se ensina a usar “instanceof” mas o correto é pegar a classe e comparar se a classe é igual.

Deixe isso pro Eclipse ou pro NetBeans (ou pro Commons BeanUtils) fazerem por você; normalmente é adequado.

Vou dar um exemplo com 4 campos, sendo que dois deles podem ser null (e nesse caso, consideraremos que null == null, em vez de achar que null != null como no SQL):

class Cliente {
    // OK, não tem getters nem setters, mas é só para mostrar como se 
	// implementa hashCode e equals.
	public long codigo; 
    public String nome; // este campo não pode ser null - só um exemplo. 
	public String endereco; // este campo pode ser null
	public BigDecimal renda; // este campo pode ser null
	
	public int hashCode () {
	     // Veja o uso do número mágico "37". Esse é o mesmo número usado 
		 // no cálculo de hashcode da classe java.lang.String.
	    int hash = Long.valueOf(codigo).hashCode();
		hash = hash * 37 + nome.hashCode();
		if (endereco != null) hash = hash * 37 + endereco.hashCode();
		if (renda != null) hash = hash * 37 + renda.hashCode();
		return hash;
	}
	public boolean equals (Object obj) {
		// As duas linhas a seguir "poderiam" ser substituídas por 
		// if (! obj instanceof Cliente) return false; 
		// mas não é a mesma coisa.
	    if (obj == null) return false;
		if (obj.getClass() != this.getClass()) return false; 
		// Não iremos distinguir entre minúsculas e maiúsculas, 
		// e null == null (diferentemente do SQL onde NULL != NULL.)
		Cliente outro = (Cliente) obj;
		if (codigo != outro.codigo) return false;
		if (!nome.equalsIgnoreCase (outro.nome)) return false;
		if ((endereco == null && outro.endereco == null) || endereco.equals (outro.endereco)) 
			if ((renda == null && outro.renda == null) || renda.equals (outro.renda))
				return true;
		return false;
	}
}
Daniel_Reis

Forma errada acredito ser um termo um tanto quanto exagerado. Eu diria ineficiente, principalmente em se tratando de certificação.

Amigo, infelizmente o termo não é exagerado, n é um erro de programação, mas um erro de implementação… pq os dois métodos estão retornando true… seria o mesmo, ou pior do q n sobrescreve-los…

Concordo com vc thingol, as IDES normalmente fazem isso por nós de uma forma mais simples e normalmente mais segura, é uma pena não ter um eclipse na hora da prova… hhehehehehee …

:thumbup:

J

Daniel Reis:
Amigo, infelizmente o termo não é exagerado, n é um erro de programação, mas um erro de implementação… pq os dois métodos estão

retornando true… seria o mesmo, ou pior do q n sobrescreve-los…


Dois métodos retornando true, onde? No código do c0m4nch3 só o método equals retorna true.

Amigo, o método hashCode está implementado de forma a retornar sempre o mesmo valor, ou seja, todos os objetos Prova serão encontrados em um mesmo “depósito”, o que torna o processo de localização ineficiente mas não errado. Veja esse trecho retirado do livro da Kathy: “É perfeitamente válido ter um método de código de hashing absolutamente ineficiente em sua classe, contanto que ele não viole o contrato especificado na documentação da classe Object”.
Concordo que as consequências dessa implementação são desastrosas, principalmente com relação ao método equals mas ainda assim ela é válida. Da maneira como foi colocada a sua resposta no primeiro post ficou parecendo que vc não conhecia essa diferença sutil entre os termos apropriado com válido ou eficiente.

Cuidado ao fazer afirmações do tipo “…o correto seria:” quando se trata de assuntos onde um termo ou a interpretação dele faz diferença. Além do mais em um outro sentido fica parecendo que só existe essa solução. Na explicação do thingol existem outras soluções, por exemplo além da apresentada eu poderia implementar utilizando um outro número primo nos cálculos do código que não o 37 e/ou o operador XOR e ainda assim poderia estar correta e eficiente. Como vc mesmo viu na explicação dele a implementação de equals que eu apresentei que por sinal foi a mesma que vc apresentou não é “correta”, no meu caso eu apenas apresentei uma maneira sem especificar se era correta, eficiente ou seja lá o que for, apenas para o c0m4nch3 sanar as suas dúvidas.

Só pra reforçar onde foquei meus comentários. No dia-a-dia com certeza eu buscaria por eficiência nas minhas implementações.

No livro da Kathy pág. 302 seção Observações para o exame e nos parágrafos subsequentes entre outras coisas fala exatamente sobre o fato de hashCode retornar sempre o mesmo valor ser válido e até mesmo apropriado. E que correto não significa necessariamente bom! Quem possuir o livro e tiver dificuldades no assunto recomendo a leitura dessa parte.

Daniel_Reis

Amigo, perdão se me expressei mal, não foi a intenção. Quando eu quis dizer dois métodos retornando true, eu quis que n estava sendo feito as checagens necessárias e retornando sempre como == … Quando eu disse “errado”, n falei q n compilaria ou algo parecido, falei de implementação errada… tenho os dois livros da Kathy 1.4 e comprei o 1.5 essa semana, e justamente por conhecer a frase: " correto (por que compila, por que aparentemente funciona) não significa necessariamente bom" é que usei o termo “errado”…

Bom, mas de qualquer forma, acho que o nosso deve ter entendido as várias formas de declarar o equals() e o hashcode().

:thumbup:

S

thingol:

// As duas linhas a seguir "poderiam" ser substituídas por // if (! obj instanceof Cliente) return false; // mas não é a mesma coisa.


Por quê? Possível subclasse de Cliente?

T

Realmente é o tal problema da subclasse de Cliente.

O que o próprio Josh Bloch ensinou é que na maior parte do tempo, usar instanceof é suficiente (e é assim que aparece na receita de bolo do Effective Java); mas se quisermos ser rigorosos, devemos checar se os objetos são exatamente da mesma classe.

Effective Java Reloaded

C

Se eu nao sobrescrever o metodo Equals() e nem metodo HashCode(), a saida seria “3”, correto?

Sabemos que o Set evita o elemento duplicado, e neste o codigo, “set” conseguiu adicionar ’ (new Prova(“SCJP”))’ porque sao referencias diferentes (nao sei se eh assim q fala).

Outra coisa, se eu implementar o metodo HashCode() a seguir:

public int HashCode(){ 

	int i = (int)(Math.random()*100);
	return i*number;
}

Este metodo é valido, mas NÃO corretamente pro causa do Math.random?

Vlw, galera e principalemnte thingol por mostrar a implementacao do HasCode() bastante eficiente

T

a) é “public int hashCode”, não “public int HashCode”.
Cuidado com a diferença entre minúsculas e maiúsculas - isso tem de ser tão natural para você quanto respirar. Depois de mais de 20 anos mexendo com informática sei quando você tem um O ou um 0, ou um l ou um 1.
b) O hashcode de um objeto tem de ser igual a cada vez que você recalculá-lo para ele mesmo, se ele não for modificado. (De preferência, se ele for modificado ele deveria também se manter constante - como é o caso da definição default de Object.hashCode). Portanto, usar Random é o pior jeito de se gerar um hashcode. Pense num hashcode como se fosse uma impressão digital do objeto. Não somente dois objetos diferentes devem ter, de preferência, hashcodes diferentes, quanto o mesmo objeto deve ter sempre o mesmo hashcode.

Criado 28 de fevereiro de 2008
Ultima resposta 3 de mar. de 2008
Respostas 13
Participantes 6