2013-06-20 7 views
6

Ich habe diese Frage zu lesen: Changing the elements in a set changes the 'equals' semanticsÄndern von Werten in HashSet

Aber ich weiß nicht, wie das Problem zu lösen, dass ich nicht ein Element in der HashSet ändern kann und es später entfernen.

Ich habe einige Beispiel Sourcecode:

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)); 
    } 
} 

Wo Testclass ist nur ein POJO, die eine Variable (plus Getter & Setter) und hat hashcode() und equals() implementiert hält.

Es gab eine Anfrage, die equals() und hashcode() - Methoden zu zeigen. Diese werden automatisch generiert durch Verdunkelung:

@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; 
} 

Das Ergebnis ist folgendes:

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): 

Wenn der Hash-Code geändert hat, kann ich nicht den Wert aus dem HashSet entfernen. Wie in der linked question, verstehe ich warum ist es so, aber ich weiß nicht, wie Sie einen geänderten Wert löschen. Gibt es eine Möglichkeit dazu?

+0

Könnten Sie bitte den Hashcode posten? – mabbas

+0

@ mabbas bearbeitet. – looper

Antwort

8

Sie stehen vor dem Problem, da die Schlüssel in Ihrem hashset nicht unveränderbar sind. Wenn Sie keine unveränderbaren Schlüssel haben, verlieren Sie die Referenz des ursprünglichen Schlüsselobjekts, sobald Sie geändert wurden. Und wird niemals in der Lage sein, das zu handhaben, was manchmal als Speicherlecks in der Sammlung bezeichnet wird. Wenn Sie also unveränderliche Schlüssel verwenden, würden Sie nicht auf diese Situation stoßen.

+0

Interessant - ist das Ergebnis des Hash-Sets tatsächlich ein Wrapper um eine Hash-Map? – robjohncox

+0

@robjohncox Es ist, weil der Weg Hashing funktioniert. Hashcodes werden zum Speichern und Abrufen von Objekten verwendet. Angenommen, Sie erstellen ein Schlüsselobjekt, und wenn Sie es in eine Hashmap einfügen, wird seine Hashcode-Methode aufgerufen, um den Hash zu berechnen und den Bucket zum Speichern zu finden. Wenn Sie versuchen, ihn abzurufen, wird erneut hashcode aufgerufen, um den Hash/Bucket zu erhalten wo Schlüssel gespeichert ist. Wenn Sie das Schlüsselobjekt nach dem Speichern im Set/Map ändern, gibt die Hashcode-Methode einen anderen Hash für dieses Schlüsselobjekt zurück, der nicht mit dem identisch ist, der zum Speichern des Schlüssels verwendet wurde. –

+1

Sehr gut, aber Referenz in HashSet (HashMap) zu suchen nicht nur Hashcode verwendet wird, sondern auch gleich verwendet wird –

1

Wenn Sie dem HashSet testElement hinzufügen, wird ein Bucket basierend auf dem Hashcode für testElement ausgewählt. Wenn Sie das HashSet fragen, wenn es ein TestElement enthält, berechnet es den Hash-Code des Objekts, nach dem es sucht, und sucht nur in diesem Bucket.

Da Ihr hashCode() auf einem nicht endgültigen Feld basiert, kann sich der Hash-Code hinter den Kulissen des HashSets ändern. Damit wird die Grundannahme des HashSet vollständig entkräftet.

Eine korrekte Implementierung für die Testclass würde das s1 Feld als endgültig haben.

2

Als die Frage, die Sie mit Details verknüpft haben, und wie andere darauf hingewiesen haben, stoßen Sie auf das Problem mit dem veränderbaren Schlüssel. Ich werde von den Javadoc requote:

Hinweis: Große Sorgfalt ausgeübt werden muß, wenn veränderbare Objekte nach Elementen verwendet werden. Das Verhalten eines Satzes wird nicht angegeben, wenn der Wert eines -Objekts in einer Weise geändert wird, die Gleichheitsvergleiche betrifft, während das Objekt ein Element in der Gruppe ist.

Wie Sie darauf hingewiesen haben, bekommen Sie das. Die Frage ist, wie entfernen Sie das Objekt tatsächlich, wenn das der Fall ist? Sie können Set.remove() nicht verwenden, weil Ihr Objekt in der Hash-Tabelle verloren geht. Sie können jedoch eine Iterator verwenden, um es zu tun.Etwas wie folgt aus:

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(); 
    } 
} 

Dieser Ansatz beruht auf der Tatsache, dass Standard-equals() Methoden, wie Sie verwenden, haben eine Instanz zu überprüfen, und dass der Check gibt true zurück.

Bitte beachten Sie, dies ist nicht die richtige Weg, um dieses Problem zu lösen. Der richtige Weg besteht darin, entweder einen unveränderlichen Schlüssel zu verwenden oder "große Sorgfalt" auszuüben, aber es ist eine Möglichkeit, ein mutiertes Objekt aus einer HashSet zu entfernen.