2010-11-07 24 views
15

Di solito non codice in Java, ma di recente non ho iniziato a scegliere. Potrei avere qualche grosso malinteso su come utilizzare correttamente HashSet. Quindi potrebbe essere possibile che qualcosa che ho fatto sia semplicemente sbagliato. Comunque sono grato per qualsiasi aiuto, potresti offrire. Quindi il problema reale:Java Hashset.contains() produce un risultato misterioso

In un piccolo programma stavo scrivendo, stavo generando oggetti molto simili, che, una volta creati, avrebbero un ID molto specifico (a long). Poiché ogni oggetto generava nuovi oggetti, volevo filtrare tutti quelli che ho già creato. Così ho iniziato a lanciare l'id di ogni nuovo oggetto nel mio Hash (Set) e testare con HashSet.contains(), se un oggetto è stato creato prima. Ecco il codice completo:

// hashtest.java 
import java.util.HashSet; 

class L { 
    public long l; 
    public L(long l) { 
     this.l = l; 
    } 
    public int hashCode() { 
     return (int)this.l; 
    } 
    public boolean equals(L other) { 
     return (int)this.l == (int)other.l; 
    } 
} 

class hashtest { 
    public static void main(String args[]) { 
     HashSet<L> hash = new HashSet<L>(); 
     L a = new L(2); 
     L b = new L(2); 
     hash.add(a); 
     System.out.println(hash.contains(a)); 
     System.out.println(hash.contains(b)); 
     System.out.println(a.equals(b)); 
     System.out.println(a.hashCode() == b.hashCode()); 
    } 
} 

produce output seguente:

true 
false 
true 
true  

così apparentemente, contains non utilizza la funzione equals fornito da L, o ho qualche grande malinteso del concetto ...

L'ho provato con openjdk (versione corrente inclusa in ubuntu) e il java corrente ufficiale da Oracle su Win7

per completezza documentazione ufficiale API Java per HashSet.contains():

public boolean contains(Object o)

Restituisce true se questo insieme contiene l'elemento specificato . Più formalmente, restituisce true se e solo se questo set contiene un elemento e tale che (o==null ? e==null : o.equals(e)).

http://download.oracle.com/javase/6/docs/api/java/util/HashSet.html#contains(java.lang.Object)

Tutte le idee o suggerimenti?

risposta

28

Il tuo metodo equals deve prendere un Object.
Poiché l'utente ha dichiarato di prendere uno L, diventa un sovraccarico aggiuntivo anziché sostituire il metodo.
Pertanto, quando la classe hashSet chiama equals, risolve il metodo base Object.equals. Quando si chiama equals, si chiama sovraccarico perché a e b sono entrambi dichiarato come L anziché Object.

Per evitare questo problema in futuro, è necessario aggiungere @Override ogni volta che si esegue l'override di un metodo.
In questo modo, il compilatore ti avviserà se in realtà non è un override.

+1

Per chiarire - se non si ha la firma della propria versione di "uguale", allora non si sta effettivamente sovraccaricando equals, e HashSet (e qualsiasi altra cosa) useranno il metodo originale equals a meno che non sappia direttamente sulla classe L (come fa il tuo codice di prova). Per verificare ciò, aggiungere: System.out.println (a.equals ((Object) b)); al tuo programma di test - dovrebbe restituire false. –

3

In realtà non stai ignorando Object.equals; invece, stai definendo un nuovo metodo con lo stesso nome ma diversi parametri. Si noti che Object.equals accetta un argomento Object, mentre il metodo equals accetta un argomento L.Se si riscrive il metodo equals per prendere un Object ed eseguire il controllo/casting di tipo necessario su L in fase di runtime, il codice funziona come previsto.

Inoltre, questo è il motivo per cui dovresti davvero usare le annotazioni @Override ogni volta che JRE le supporta. In questo modo, il compilatore si lamenterà se si implementa accidentalmente un nuovo metodo quando si intende eseguire l'override di uno esistente.

A titolo di esempio, questo metodo equals dovrebbe funzionare correttamente. (E, su una nota indipendenti, esso non sicuro se l'oggetto rispetto a è nullo.)

@Override 
public boolean equals(Object other) { 
    return other != null && other instanceof L && this.l == ((L)other).l; 
} 
+0

Ah ah, l'ho fatto davvero. Il mio compilatore mentale non deve funzionare oggi. :) – bcat

3

Quando si aggiungono oggetti a un insieme che definisce internamente equals e hashCode metodi. Devi scavalcare questi due metodi. Ad esempio, ho preso una classe bean con name, id, designation, quindi creato e aggiunto un oggetto employee.

HashSet<Employee> set = new HashSet<Employee>(); 
Employee employee = new Employee(); 
employee.setId(1); 
employee.setName("jagadeesh"); 
employee.setDesignation("programmer"); 
set.add(employee); 
Employee employee2 = new Employee(); 
employee2.setId(1); 
employee2.setName("jagadeesh"); 
employee2.setDesignation("programmer"); 
set.add(employee2); 

set.add() chiamate internamente le equals e hashCode metodi. Quindi devi sostituire questi due metodi nella classe bean.

@Override 
public int hashCode(){ 
    StringBuffer buffer = new StringBuffer(); 
    buffer.append(this.name); 
    buffer.append(this.id); 
    buffer.append(this.designation); 
    return buffer.toString().hashCode(); 
} 
@Override 
public boolean equals(Object object){ 
    if (object == null) return false; 
    if (object == this) return true; 
    if (this.getClass() != object.getClass())return false; 
    Employee employee = (Employee)object; 
    if(this.hashCode()== employee.hashCode())return true; 
    return false; 
} 

Qui stiamo imperativi equals() e hashCode(). Quando aggiungi un oggetto al metodo HashSet, itera internamente tutti gli oggetti e chiama il metodo equals. Quindi abbiamo sostituito lo hashCode, confronta tutti gli oggetti hashCode con il suo attuale hashCode e restituisce true se entrambi sono uguali, altrimenti restituisce false.

Problemi correlati