2013-06-20 14 views
6

ho letto questa domanda: Changing the elements in a set changes the 'equals' semanticsModifica dei valori in HashSet

Tuttavia, non so come risolvere il problema che non posso cambiare una voce del HashSet e rimuoverlo in seguito.

ho qualche esempio di codice sorgente:

public static void main(String[] args) { 
    TestClass testElement = new TestClass("1"); 
    Set<TestClass> set = new HashSet<>(); 
    set.add(testElement); 
    printIt(testElement, set, "First Set"); 
    testElement.setS1("asdf"); 
    printIt(testElement, set, "Set after changing value"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set after trying to remove value"); 
    testElement.setS1("1"); 
    printIt(testElement, set, "Set after changing value back"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set removing value"); 
} 

private static void printIt(TestClass hullo, Set<TestClass> set, String message) { 
    System.out.println(message + " (hashCode is " + hullo.hashCode() + "):"); 
    for (TestClass testClass : set) { 
     System.out.println(" " + testClass.toString()); 
     System.out.println("  HashCode: " + testClass.hashCode()); 
     System.out.println("  Element is equal: " + hullo.equals(testClass)); 
    } 
} 

Dove TestClass è solo un POJO che detiene una variabile (più getter & setter) e dispone di codice hash() e equals() implementato.

C'era una richiesta per mostrare i metodi equals() e hashcode() -. Questi sono generati automaticamente da eclisse:

@Override 
public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((s1 == null) ? 0 : s1.hashCode()); 
    return result; 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) 
     return true; 
    if (obj == null) 
     return false; 
    if (getClass() != obj.getClass()) 
     return false; 
    TestClass other = (TestClass) obj; 
    if (s1 == null) { 
     if (other.s1 != null) 
      return false; 
    } else if (!s1.equals(other.s1)) 
     return false; 
    return true; 
} 

Il risultato è il seguente:

First Set (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set after changing value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after trying to remove value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after changing value back (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set removing value (hashCode is 80): 

Quando il codice hash è cambiato, non posso rimuovere il valore dal HashSet. Come nel linked question, capisco perché è lo stesso, ma non so come cancellare un valore modificato. C'è qualche possibilità di farlo?

+0

Potresti postare i metodi hashcode ed equals per favore? – mabbas

+0

@mabbas modificato. – looper

risposta

8

Sei di fronte al problema perché le chiavi nel tuo hashset non sono immutabili. Se non si dispone di chiavi immutabili, si perderà il riferimento dell'oggetto chiave originale una volta modificato. E non sarà mai in grado di gestirlo, che a volte viene indicato come perdite di memoria nella raccolta. Quindi se usi le chiavi immutabili, non ti imbatterai in questa situazione.

+0

Interessante: il risultato del set di hash è in realtà un wrapper attorno a una mappa hash? – robjohncox

+0

@robjohncox È perché il modo in cui funziona l'hashing. I codici hash vengono utilizzati per archiviare e recuperare oggetti. Supponiamo che tu crei un oggetto chiave e quando lo metti in una hashmap, il suo metodo hashcode verrà chiamato per calucolare l'hash e trovare il bucket da memorizzare. Quando provi a recuperarlo, viene chiamato nuovamente hashcode per ottenere l'hash/bucket dove viene memorizzata la chiave. Se si modifica l'oggetto chiave dopo averlo memorizzato nella serie/mappa, il metodo hashcode restituirà un hash diverso per quell'oggetto chiave, che non è lo stesso utilizzato per memorizzare la chiave. –

+1

Molto buono ma per cercare il riferimento in HashSet (hashmap) non viene usato solo hashcode, ma viene utilizzato ugualmente –

1

Quando si aggiunge testElement a HashSet, seleziona un bucket basato sul codice hash per testElement. Quando chiedi allo HashSet se contiene uno TestElement, calcola il codice hash dell'oggetto che sta cercando e cerca solo in quel segmento.

Poiché il tuo si basa su un campo non finale, il codice hash può cambiare dietro le quinte di HashSet. Quindi invalidando completamente l'assunto di base di HashSet.

Un'implementazione corretta per Testclass avrebbe il campo s1 come finale.

2

Come la domanda che hai collegato ai dettagli, e come altri hanno sottolineato, stai riscontrando il problema della chiave mutabile. Io requote dal Javadoc:

Nota: Grande attenzione deve essere esercitata se gli oggetti mutabili sono utilizzati come set elementi. Il comportamento di un set non viene specificato se il valore di un oggetto viene modificato in un modo che influisce su confronti uguali mentre l'oggetto è un elemento nel set.

Come hai sottolineato, lo ottieni. La domanda è, come si rimuove effettivamente l'oggetto dato che è il caso? Non è possibile utilizzare Set.remove(), perché l'oggetto è perso nella tabella hash. Tuttavia, è possibile utilizzare uno Iterator per farlo.Qualcosa di simile a quanto segue:

TestClass toRemove = <the same instance, but mutated>; 
for (Iterator<TestClass> iter = set.iterator(); iter.hasNext();) { 
    TestClass item = iter.next(); 
    if (toRemove.equals(item)) { 
    iter.remove(); 
    } 
} 

Questo approccio si basa sul fatto che standard di equals() metodi, come si sta utilizzando, hanno un controllo di esempio, e che il check restituirà true.

Si prega di tenere presente questo non è il destra modo per risolvere questo problema. Il modo corretto è utilizzare una chiave immutabile o "esercitare grande cura", ma è un modo per rimuovere un oggetto mutato da un HashSet.

Problemi correlati