HashSet.supprimer () et itérateur.remove() ne fonctionne pas

J'ai des problèmes avec Iterator.remove() appelé sur un HashSet.

J'ai un ensemble d'objets horodatés. Avant d'ajouter un nouvel élément à L'ensemble, je boucle l'ensemble, identifie une ancienne version de cet objet de données et le supprime (avant d'ajouter le nouvel objet). l'horodatage est inclus dans hashCode et equals(), mais pas equalsData().

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

La chose étrange est que I. remove() échoue silencieusement (sans exception) pour certains des éléments de l'ensemble. J'ai vérifié

  • La ligne I. remove() est réellement appelée. Je peux l'appeler à partir du débogueur directement au point D'arrêt dans Eclipse et il ne parvient toujours pas à changer L'état de Set

  • Dataaresult est un objet immuable, il ne peut donc pas avoir changé après avoir été ajouté à l'ensemble à l'origine.

  • Les méthodes equals et hashCode() utilisent @Override pour s'assurer qu'elles sont les bonnes méthodes. Les tests unitaires vérifient ces travaux.

  • Cela échoue également si je viens un énoncé.supprimer à la place. (par exemple, parcourez les éléments, trouvez l'élément dans la liste, puis appelez Set.supprimer(oldData) après la boucle).

  • J'ai testé dans JDK 5 et JDK 6.

Je pensais que je devais manquer quelque chose de basique, mais après avoir passé un certain temps important sur ce mon collègue et moi sommes perplexes. Des suggestions pour les choses à vérifier?

Modifier:

Il y a eu des questions - Dataaresult est-il vraiment immuable. Oui. Il n'y a pas setter. Et lorsque L'objet Date est récupéré (qui est un objet mutable), il est fait en créant une copie.

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

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

Modification ultérieure (quelque temps plus tard): Pour mémoire -- DataResult n'était pas immuable! Il a référencé un objet qui avait un hashcode qui a changé lorsqu'il est persistant dans la base de données (mauvaise pratique, je sais). Il s'est avéré que si un DataResult était créé avec un sous-objet transitoire et que le sous-objet était persistant, le hashcode DataResult était modifié.

Très subtile -- Je regardé cela plusieurs fois et n'a pas remarqué le manque d'immuabilité.

31
demandé sur Will Glass 0000-00-00 00:00:00

7 réponses

J'étais encore très curieux de celui-ci, et j'ai écrit le test suivant:

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

Qui se traduit par:

1
1

Si la valeur hashCode () d'un objet a changé depuis qu'il a été ajouté au HashSet, il semble rendre l'objet inamovible.

Je ne suis pas sûr si c'est le problème que vous rencontrez, mais c'est quelque chose à examiner si vous décidez de visiter à nouveau ceci.

39
répondu Jack Leow 2008-11-01 23:27:26

Sous les couvertures, HashSet utilise HashMap, qui appelle HashMap.removeEntryForKey (objet) lorsque L'un ou L'autre HashSet.supprimer (objet) ou itérateur.remove() est appelé. Cette méthode utilise à la fois hashCode () et equals () pour valider qu'elle supprime l'objet approprié de la collection.

Si les deux itérateurs.supprimer () et HashSet.remove (Object) ne fonctionne pas, alors quelque chose ne va pas avec vos méthodes equals () ou hashCode (). L'affichage du code pour ceux-ci serait utile dans le diagnostic de votre question.

6
répondu Spencer Kormos 2008-11-01 05:01:56

Êtes-vous absolument certain que DataResult est immuable? Quel est le type de l'horodatage? Si c'est un java.util.Date en faites-vous des copies lorsque vous initialisez le DataResult? Gardez à l'esprit que java.util.Date est mutable.

Par exemple:

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

Imprimerait deux fois différentes.

Cela aiderait aussi si vous pouviez poster du code source.

2
répondu Jack Leow 2008-11-01 03:37:23

Merci pour toute l'aide. Je soupçonne que le problème doit être avec equals() et hashCode() comme suggéré par spencerk. J'ai vérifié ceux dans mon Débogueur et avec des tests unitaires, mais je dois manquer quelque chose.

J'ai fini par faire une solution de contournement-copier tous les éléments sauf un dans un nouvel ensemble. Pour les coups de pied, J'ai utilisé Apache Commons CollectionUtils.

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

Je vais m'arrêter ici-trop de travail pour simplifier jusqu'à un cas de test simple. Mais l'aide est miuch apprécié.

2
répondu Will Glass 2008-11-01 21:02:47

Vous devriez tous faire attention à toute Collection Java qui récupère ses enfants par hashcode, dans le cas où le hashcode de son type enfant dépend de son état mutable. Un exemple:

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

HashSet récupère un élément par son hashCode, mais son type d'élément est un HashSet, et hashSet.hashCode dépend de l'état de son élément.

Code d'ailleurs:

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)

La raison est que la méthode remove de HashSet utilise HashMap et identifie les clés par hashCode, tandis que le hashCode de AbstractSet est dynamique et dépend des propriétés mutables de lui-même.

2
répondu Tomer Shalev 2017-03-04 00:27:04

Avez-vous essayé quelque chose comme

boolean removed = allResults.remove(oldData)
if (!removed) // COMPLAIN BITTERLY!

En d'autres termes, supprimez l'objet de L'ensemble et cassez la boucle. Cela ne fera pas que le Iterator se plaint. Je ne pense pas que ce soit une solution à long terme mais vous donnerait probablement quelques informations sur le hashCode, equals et equalsData méthodes

1
répondu Ken Gentle 2008-10-31 18:40:22

C'est presque certainement le cas où les hashcodes ne correspondent pas aux anciennes et nouvelles données qui sont "equals()". J'ai déjà rencontré ce genre de chose et vous finissez essentiellement par cracher des hashcodes pour chaque objet et la représentation de la chaîne et essayer de comprendre pourquoi l'inadéquation se produit.

Si vous comparez des éléments de base de données pré/post, il perd parfois les nanosecondes (en fonction de votre type de colonne DB) qui peuvent

1
répondu