44 Stimmen

HashSet.remove() und Iterator.remove() funktionieren nicht

Ich habe Probleme mit Iterator.remove() auf ein HashSet aufgerufen.

Ich habe eine Reihe von zeitgestempelten Objekten. Bevor ich dem Set ein neues Element hinzufüge, durchlaufe ich das Set, identifiziere eine alte Version dieses Datenobjekts und entferne sie (bevor ich das neue Objekt hinzufüge). Der Zeitstempel ist in hashCode und equals() enthalten, aber nicht in equalsData().

for (Iterator<DataResult> i = allResults.iterator(); i.hasNext();)
{
    DataResult oldData = i.next();
    if (data.equalsData(oldData))
    {   
        i.remove();
        break;
    }
}
allResults.add(data)

Das Seltsame ist, dass i.remove() für einige Elemente in der Menge stillschweigend fehlschlägt (keine Ausnahme). Ich habe überprüft

  • Die Zeile i.remove() wird tatsächlich aufgerufen. Ich kann sie vom Debugger aus direkt am Haltepunkt in Eclipse aufrufen und es gelingt mir immer noch nicht, den Zustand von Set

  • DataResult ist ein unveränderliches Objekt, das sich also nicht mehr ändern kann, nachdem es der Menge ursprünglich hinzugefügt wurde.

  • Die Methoden equals und hashCode() verwenden @Override, um sicherzustellen, dass es sich um die richtigen Methoden handelt. Unit-Tests überprüfen, ob sie funktionieren.

  • Dies schlägt auch fehl, wenn ich stattdessen einfach eine for-Anweisung und Set.remove verwende. (z. B. Schleife durch die Elemente, finden Sie das Element in der Liste, dann rufen Sie Set.remove(oldData) nach der Schleife).

  • Ich habe mit JDK 5 und JDK 6 getestet.

Ich dachte, ich müsste etwas Grundlegendes übersehen haben, aber nachdem wir einige Zeit damit verbracht haben, sind mein Kollege und ich ratlos. Haben Sie Vorschläge, was man überprüfen könnte?

EDIT:

Es gab Fragen - ist DataResult wirklich unveränderlich. Ja. Es gibt keine Setzer. Und wenn das Date-Objekt abgerufen wird (das ein veränderbares Objekt ist), wird eine Kopie davon erstellt.

public Date getEntryTime()
{
    return DateUtil.copyDate(entryTime);
}

public static Date copyDate(Date date)
{
    return (date == null) ? null : new Date(date.getTime());
}

FURTHER EDIT (einige Zeit später): Für das Protokoll - DataResult war nicht unveränderlich! Es verwies auf ein Objekt, das einen Hashcode hatte, der sich änderte, wenn es in der Datenbank gespeichert wurde (schlechte Praxis, ich weiß). Es stellte sich heraus, dass sich der Hashcode von DataResult änderte, wenn ein DataResult mit einem transienten Unterobjekt erstellt wurde und das Unterobjekt persistiert wurde.

Sehr subtil - ich habe mir das mehrmals angeschaut und nicht bemerkt, dass es nicht unveränderbar ist.

0 Stimmen

Zwei Möglichkeiten. 1. Sie sagen, dass DataResult unveränderlich ist. Kann man davon ausgehen, dass die Werte durch den Konstruktor gesetzt werden und es keine Set-Methoden gibt? 2. Ihre Gleichheits- und Hashcodes funktionieren nicht so, wie Sie es erwarten. Können Sie den Code für diese beiden posten?

59voto

Jack Leow Punkte 21300

Ich war immer noch sehr neugierig darauf und habe den folgenden Test geschrieben:

import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

public class HashCodeTest {
    private int hashCode = 0;

    @Override public int hashCode() {
        return hashCode ++;
    }

    public static void main(String[] args) {
        Set<HashCodeTest> set = new HashSet<HashCodeTest>();

        set.add(new HashCodeTest());
        System.out.println(set.size());
        for (Iterator<HashCodeTest> iter = set.iterator();
                iter.hasNext();) {
            iter.next();
            iter.remove();
        }
        System.out.println(set.size());
    }
}

was zur Folge hat:

1
1

Wenn sich der HashCode()-Wert eines Objekts geändert hat, seit es dem HashSet hinzugefügt wurde, scheint es das Objekt unentfernbar zu machen.

Ich bin mir nicht sicher, ob das das Problem ist, auf das Sie stoßen, aber es ist etwas, das Sie sich ansehen sollten, wenn Sie sich entscheiden, das Problem erneut aufzugreifen.

0 Stimmen

Danke - das weiß ich sehr zu schätzen. Ich vermute, Sie könnten Recht haben. Werde meine Unit-Tests für equals/hashCode auf das Basisobjekt überarbeiten.

5 Stimmen

Sechs Jahre später hat mir diese Antwort weitere Stunden der Interaktion zwischen Kopf und Schreibtisch erspart. +1. +allthe1s.

1 Stimmen

+1; Diese Lösung hat mir gerade geholfen, einen frustrierenden Fehler bei der Erstellung eines Satzes von Untergraphen zu lösen. Ich möchte hinzufügen, dass der Grund für dieses Auftreten zu sein scheint, dass HashSet HashMap umhüllt. Die Werte im Hash werden als Schlüssel und nicht als Werte behandelt. Natürlich können Schlüssel nicht veränderbar sein, aber es ist frustrierend, dass die intuitive Vorstellung, dass Mengen durch Iteration zusammengesetzt werden, verletzt wird...

7voto

Spencer Kormos Punkte 8261

Unter der Haube verwendet HashSet HashMap, das HashMap.removeEntryForKey(Object) aufruft, wenn entweder HashSet.remove(Object) oder Iterator.remove() aufgerufen wird. Diese Methode verwendet sowohl hashCode() als auch equals(), um zu überprüfen, ob sie das richtige Objekt aus der Sammlung entfernt.

Wenn sowohl Iterator.remove() als auch HashSet.remove(Object) nicht funktionieren, dann stimmt definitiv etwas mit Ihren equals() oder hashCode() Methoden nicht. Die Angabe des Codes für diese Methoden wäre für die Diagnose Ihres Problems hilfreich.

4voto

Jack Leow Punkte 21300

Sind Sie absolut sicher, dass DataResult unveränderlich ist? Was ist der Typ des Zeitstempels? Wenn es sich um einen java.util.Date erstellen Sie Kopien davon, wenn Sie das DataResult initialisieren? Denken Sie daran, dass java.util.Date ist veränderbar.

Zum Beispiel:

Date timestamp = new Date();
DataResult d = new DataResult(timestamp);
System.out.println(d.getTimestamp());
timestamp.setTime(System.currentTimeMillis());
System.out.println(d.getTimestamp());

Würde zwei verschiedene Zeiten drucken.

Es wäre auch hilfreich, wenn Sie etwas Quellcode veröffentlichen könnten.

0 Stimmen

@Jack Leow: Das ist ein toller Kommentar. Allerdings kopiere ich das Datumsobjekt, bevor ich es über den Getter freigebe. (Ich habe kürzlich Ausgabe 2 von Blochs Effectiva Java gelesen). DataResult ist also wirklich unveränderlich.

0 Stimmen

Kopieren des Wertes oder der Referenz?

0 Stimmen

@Blade: erzeugt eine neue Instanz von Date. Siehe Bearbeitung der ursprünglichen Frage.

4voto

Tomer Shalev Punkte 39

Sie sollten sich vor jeder Java-Sammlung in Acht nehmen, die ihre Kinder per Hashcode abruft, wenn der Hashcode ihres Kindtyps von ihrem veränderlichen Zustand abhängt. Ein Beispiel:

HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap variant:

HashSet ruft ein Element anhand seines HashCodes ab, aber sein Elementtyp ist ein HashSet, und hashSet.hashCode hängt vom Zustand des Elements ab.

Code für diese Angelegenheit:

HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>();
HashSet<String> set1 = new HashSet<String>();
set1.add("1");
coll.add(set1);
print(set1.hashCode()); //---> will output X
set1.add("2");
print(set1.hashCode()); //---> will output Y
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY)

Der Grund dafür ist, dass die remove-Methode von HashSet die HashMap verwendet und die Schlüssel durch den HashCode identifiziert, während der HashCode von AbstractSet dynamisch ist und von den veränderlichen Eigenschaften des HashSets abhängt.

3voto

Will Glass Punkte 4690

Vielen Dank für die Hilfe. Ich vermute, dass das Problem mit equals() und hashCode() liegt, wie von spencerk vorgeschlagen. Ich habe diese in meinem Debugger und mit Unit-Tests überprüft, aber ich habe etwas zu übersehen.

Am Ende habe ich einen Ausweg gefunden: Ich habe alle Elemente bis auf eines in ein neues Set kopiert. Zum Spaß habe ich Apache Commons CollectionUtils verwendet.

    Set<DataResult> tempResults = new HashSet<DataResult>();
    CollectionUtils.select(allResults, 
            new Predicate()
            {
                public boolean evaluate(Object oldData)
                {
                    return !data.equalsData((DataResult) oldData);
                }
            }
            , tempResults);
    allResults = tempResults;

Ich werde hier aufhören - zu viel Arbeit, um es auf einen einfachen Testfall zu vereinfachen. Aber die Hilfe ist miuch geschätzt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X