Jump to content
Nazgulled

HashSet está a repetir-me objectos diferentes com valores iguais

Recommended Posts

Nazgulled

Segundo percebi, o HashSet não deixa duplicar objectos. Mas segundo algo que encontrei na net, ele chama o método equals() do tipo de objectos passado para o HashSet para verificar se o objecto já existe. Ele faz mesmo isso? É que eu tenho aqui um exemplo que não está a funcionar...

public class Ficha {
private String nome;

public Ficha(String nome) {
	this.nome = nome;
}

public String getNome() {
	return this.nome;
}

public boolean equals(Ficha ficha) {
	if(ficha == this) return true;
	if(ficha == null) return false;

	if(!this.nome.equals(ficha.getNome())) return false;

	return true;
}

public String toString() {
	StringBuilder str = new StringBuilder();

	str.append("[ NOME: ");
	str.append(this.nome);
	str.append(" ]");

        return str.toString();
}
}

public class Teste {
public static void main(String args[]) {
	HashSet<Ficha> fichas = new HashSet<Ficha>();

	Ficha f1 = new Ficha("Tiago");
	Ficha f2 = new Ficha("Sofia");

	fichas.add(f1);
	fichas.add(f2);
	fichas.add(f1);

	Ficha f3 = new Ficha("Sofia");

	fichas.add(f3);

	System.out.println(fichas.toString());
}
}

O output deste código é:

[[ NOME: Sofia ], [ NOME: Sofia ], [ NOME: Tiago ]]

Se o HashSet estivesse a usar o equals() da classe Ficha, apenas deveria ter uma Sofia e não duas...

Estou a fazer alguma mal ou é mesmo assim e não há volta a dar?

Share this post


Link to post
Share on other sites
Knitter

O método equals tem de receber um object ou então não estás a redefinir o método correcto.

Atenção às assinaturas dos métodos.

Implementação alternativa:

public boolean equals(Object obj) {
    if(ficha == this) return true;

    if(!(obj instanceof Ficha) return false;
    
    Ficha f = (Ficha) obj;
    return this.nome.equals(f.nome);    
}

Uma correcção, não vale a pena verificares se o objecto passado é null, ao fazeres obj instanceof Ficha, se obj for null então o resultado é falso.

Share this post


Link to post
Share on other sites
Nazgulled

Esqueci-me, no meu código também tenho uma redefinição assim:

public boolean equals(Object object) {
if(object == this) return true;
if(object == null) return false;

if (this.getClass() != object.getClass()) return false;

return this.equals((Ficha)object);
}

E continuou a não dar...

Também tentei com o teu código, após substituir ficha == this por obj == this e adicionar um parêntesis final ao segundo if se não o código não compilava e, mesmo assim não deu. O output foi o mesmo...

Share this post


Link to post
Share on other sites
Baderous

O teu não dá porque estás a invocar o equals de Object, na última instrução.

Sendo X uma classe, o método equals ficaria:

public boolean equals(Object obj) {
        if (this==obj) return true;
        if ((obj==null) || (this.getClass()!=obj.getClass())) return false;
        X p = (X)obj;
        return (algoritmo de comparação...);
    }

Share this post


Link to post
Share on other sites
Nazgulled

Não lês-te o meu último post... :D

Estava aqui a fazer debug e cheguei a uma conclusão... Algures quando ele esta a adicionar uma nova ficha ao HashSet, ele executa este pedaço de código:

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

E como podem ver, existe ali um key.equals(k) o qual não nem se quer é invocado... O que me parece é que ele nem entra no for.

Share this post


Link to post
Share on other sites
Knitter

Também tentei com o teu código, após substituir ficha == this por obj == this e adicionar um parêntesis final ao segundo if se não o código não compilava e, mesmo assim não deu. O output foi o mesmo...

Desculpa a parte dos erros fiz o código sem ter olhado muito.

O teu não dá porque estás a invocar o equals de Object, na última instrução.

Isso não implicaria erro nenhum, a única coisa que poderia causar problemas seria o facto de o método não estar implementado, mas dado que neste caso é uma string, não seria um problema.

Tanto o método equals do elemento como o da chave têm de estar implementados. Não sei que chave estás a tentar usar mas se for um objecto teu terás de implementar o método.

Claro que o problema poderá estar noutra parte mas dado o código que colocaste não dá para ver muito.

Outra coisa, não te recomendo teres mais que um método equals, um basta e por vezes mais que um pode causar problemas porque nem sempre sabes qual vai ser chamado.

Share this post


Link to post
Share on other sites
Nazgulled

Eu estou a programar tal e qual como o prof quer que a gente o faça, por isso vou deixar o código como está. E além do mais, da forma como eles estão implementados, não vejo qualquer problema que possa existir independentemente de qual dos métodos for chamado.

Quanto ao problema, disseram-me que tinha que implementar o hashCode também. Foi isso que disseste Knitter? Como o faço? Podem dar ai um exemplo?

Share this post


Link to post
Share on other sites
Knitter

Por acaso tinha esquecido o hashcode, e sim, ele também precisa ser implementado.

O hashcode deve ser implementado usando os valores que são usados na comparação.

Este é um exemplo de um par de equals/hash que retirei do meu projecto, por isso ignora o que te pareça a mais :D . Nota apenas que os campos usados no equals são usados no hash. Os valores inteiros são valores aleatórios.

    /**
     * Equals method doesn't take into account the id of the object. Hibernate 
     * only gives an id to a new object when that object is persisted, it is 
     * therefor not possible to use the id attribute.
     * 
     * A <em>Bussines Key</em> is used instead.
     * @param obj the object to compare
     * @return true if both objects are equal under the <em>Bussines Key</em>
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Media)) {
            return false;
        }

        Media other = (Media) obj;
        return name.equals(other.name) && description.equals(other.description) && type == other.type;
    }

    /**
     * HashCode for this object, excluding the id field.
     * @return integer representing the hashcode
     * 
     * @see equals
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 37 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 37 * hash + (this.description != null ? this.description.hashCode() : 0);
        hash = 37 * hash + this.type;
        return hash;
    }

Se estiveres a usar o Netbeans e implementares o método equals, ele sugere-te que implementes o hash e até te cria o código automaticamente usando os campos certos. Se não tiveres o método equals podes usar o opção para inserir código e tens lá a opção para criar o hash e escolher os campos a usar.

Se tens dois métodos iguais então um deles não serve para nada, se por acaso um deles tiver alguma diferença, então, dado que não sabes qual dos dois é invocado, podes ter resultados diferentes na mesma situação.

Porque é que precisas de dois métodos?

Share this post


Link to post
Share on other sites
Nazgulled

Resumindo, estas a implementar um método que devolve um hashcode conforme os valores das variáveis de instÂncia de tal forma que seja igual se todos os valores forem iguais entre vários objectos, correcto?

Só não estou a perceber aquele hash = 7 e depois hash = 37 * hash. É mesmo necessário?

Não poderia fazer algo mais simples como somar o hashCode() de cada uma das variáveis de instância (supondo que nenhuma delas está vazia)?

Share this post


Link to post
Share on other sites
Baderous
Não sendo fácil criar "boas" soluções de hashCode() para os nossos objectos, existem no entanto algumas regras simples que conduzem, em geral, a bons resultados:

- Na criação do hashcode usar valores das variáveis de instância que são relevantes no objecto, isto é, mais identificativas e deferenciadoras;

- Para variáveis de instância de tipos como String, Integer, etc., pode ser usado o próprio método hashCode() destas classes;

- Nos cálculos que conduzem à criação do hashcode único, usar pesos que sejam números primos.

Para variáveis de tipo referenciado, se o seu valor for null o seu hashValue deverá ser 0, senão, deverá invocar-se o respectivo hashCode().

Acho que é daquela regra que vem o 7 e o 37.

Share this post


Link to post
Share on other sites
Rui Carlos

Não poderia fazer algo mais simples como somar o hashCode() de cada uma das variáveis de instância (supondo que nenhuma delas está vazia)?

É uma questão de produzir melhores valores na função de hash.

Share this post


Link to post
Share on other sites
Rui Carlos

Usar um HashSet, tendo os elementos essa função de hash, é simplesmente ridículo...

EDIT: Já agora, se seguires à risca as regras de encapsulamento que o MM ensina em POO, nunca terás o problema de que ele fala, pois quando introduzires um valor no HashSet, não introduzes o valor, mas uma cópia.

Share this post


Link to post
Share on other sites
Baderous

@Baderous

Não percebi a primeira regra e já agora, de onde tiraste isso? Do livro do prof?

A primeira regra aplica-se, por ex, se tiveres um Ponto descrito por um X, um Y e um boolean que indica se é meu ou teu (lol). É mais lógico usar o hashCode do X e do Y para calcular o hashCode final, visto que essas 2 variáveis são mais identificadoras do objecto. Tirei do livro dele, só tem 1 página sobre isto. :x

Share this post


Link to post
Share on other sites
Nazgulled

Pronto, então no meu exemplo real (que é as fichas de tripulação do trabalho que vocês sabem qual é), tenho o seguinte:

public int hashCode() {
	int hash = this.funcao.hashCode();

        hash = 5 * hash + (this.nome != null ? this.nome.hashCode() : 0);
        hash = 5 * hash + (this.nacionalidade != null ? this.nacionalidade.hashCode() : 0);

        return hash;
}

Gosto mais do valor 5 ;), está bem ou veem algum problema nisto? Tendo em consideração as regras de encapsulamento que o MM ensina como disse o Rui Carlos, não vou ter problemas?

Share this post


Link to post
Share on other sites
Knitter

Resumindo, estas a implementar um método que devolve um hashcode conforme os valores das variáveis de instÂncia de tal forma que seja igual se todos os valores forem iguais entre vários objectos, correcto?

Sim.

Só não estou a perceber aquele hash = 7 e depois hash = 37 * hash. É mesmo necessário?

Não poderia fazer algo mais simples como somar o hashCode() de cada uma das variáveis de instância (supondo que nenhuma delas está vazia)?

A parte do 7 é para devolver um número primo se todos os outros elementos forem zero.

Mas sim, poderias ter como indicaste, mas isso resultaria num código menos bom.

Onde o gajo no final simplesmente sugere a fazer isto:

public int hashCode() { return 0; }

O que têm a dizer sobre isto?

Não fui ler o texto, mas pelo que citaste, diria que para qualquer instância que ele vai criar o hash é sempre o mesmo, ora isso difere um pouco do objectivo de um código de hash, e derrota a vantagem de ter um hash para cada instância, bem como produz resultados estranhos em alguns métodos que se baseiem em hash para identificar o objecto.

Claro que dependerá do objectivo, mas à primeira vista diria que essa opção é errada. Faz-me até pensar que código colocaria ele em classes diferentes? É que se fizer o mesmo código, então duas instâncias de duas classes diferentes irão ser identificadas como sendo o mesmo objecto.

Share this post


Link to post
Share on other sites
Rui Carlos

Pronto, então no meu exemplo real (que é as fichas de tripulação do trabalho que vocês sabem qual é), tenho o seguinte:

public int hashCode() {
	int hash = this.funcao.hashCode();

        hash = 5 * hash + (this.nome != null ? this.nome.hashCode() : 0);
        hash = 5 * hash + (this.nacionalidade != null ? this.nacionalidade.hashCode() : 0);

        return hash;
}

Gosto mais do valor 5 ;), está bem ou veem algum problema nisto? Tendo em consideração as regras de encapsulamento que o MM ensina como disse o Rui Carlos, não vou ter problemas?

Por acaso vais andar a editar os valores que estão no HashSet? Não me parece, por isso não terás problemas. Caso precisasses de editar, só tinhas que remover o valor, e voltar a colocá-lo no HashSet.

Share this post


Link to post
Share on other sites
Nazgulled

A parte do 7 é para devolver um número primo se todos os outros elementos forem zero.

Mas sim, poderias ter como indicaste, mas isso resultaria num código menos bom.

Em quê que o meu código se torna menos bom que o teu?

Share this post


Link to post
Share on other sites
Knitter

Nos cálculos que conduzem à criação do hashcode único, usar pesos que sejam números primos.

Não poderia fazer algo mais simples como somar o hashCode() de cada uma das variáveis de instância (supondo que nenhuma delas está vazia)?

Neste caso, se não garantires a existência de um peso que seja número primo.

Não estava a responder ao código que colocaste mas sim à pergunta.

Share this post


Link to post
Share on other sites
Nazgulled

Ah.... então o meu código como usa ali o 5, é primo, logo tá bom?

E já agora, será que aquele if == null é mesmo preciso?

As minhas variáveis de instância nunca serão nulas, no máximo serão vazias, mas nulas não me parece. Porque o input será sempre validado antes de ser inserido, logo, se o utilizador não inserir nada, eu nem se quer vou criar o objecto (do tipo respectivo).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.