430 votes

Obtenir un élément d'un ensemble

Pourquoi les Set fournir une opération permettant d'obtenir un élément égal à un autre élément ?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

Je peux demander si le Set contient un élément égal à bar Pourquoi ne puis-je pas obtenir cet élément ? :(

Pour plus de clarté, le equals est surchargée, mais elle ne vérifie qu'un seul des champs, et non tous. Ainsi, deux Foo des objets considérés comme égaux peuvent en fait avoir des valeurs différentes, c'est pourquoi je ne peux pas simplement utiliser foo .

2 votes

Ce sujet a déjà été largement débattu et de bonnes réponses ont été proposées. Cependant, si vous cherchez simplement un ensemble ordonné, utilisez simplement SortedSet et ses implémentations, qui sont basées sur des cartes (par ex. TreeSet permet d'accéder first() ).

3 votes

Cette méthode me manque aussi, pour exactement le même cas que celui que vous avez décrit ci-dessus. Objective-C ( NSSet ) dispose d'une telle méthode. Elle s'appelle member et renvoie l'objet de l'ensemble qui est "égal" au paramètre de la fonction member (qui peut bien sûr être un objet différent et avoir des propriétés différentes, que equal ne peut pas vérifier).

462voto

jschreiner Points 741

Pour répondre à la question précise " Pourquoi ne Set fournir une opération permettant d'obtenir un élément égal à un autre élément ?", la réponse serait : parce que les concepteurs du cadre de collecte n'étaient pas très prévoyants. Ils n'ont pas anticipé votre cas d'utilisation très légitime, ont naïvement essayé de "modéliser l'abstraction des ensembles mathématiques" (d'après la javadoc) et ont tout simplement oublié d'ajouter l'élément utile get() méthode.

Passons maintenant à la question implicite " comment Vous obtenez l'élément alors" : Je pense que la meilleure solution est d'utiliser un Map<E,E> au lieu d'un Set<E> pour faire correspondre les éléments entre eux. De cette manière, vous pouvez efficacement récupérer un élément de l'"ensemble", car la méthode get() de l'élément Map trouvera l'élément à l'aide d'une table de hachage efficace ou d'un algorithme d'arbre. Si vous le souhaitez, vous pouvez écrire votre propre implémentation de Set qui offre l'avantage supplémentaire de get() en encapsulant la méthode Map .

Les réponses suivantes sont à mon avis mauvaises ou erronées :

"Vous n'avez pas besoin d'obtenir l'élément, car vous avez déjà un objet égal" : l'affirmation est fausse, comme vous l'avez déjà montré dans la question. Deux objets qui sont égaux peuvent toujours avoir un état différent qui n'est pas pertinent pour l'égalité de l'objet. Le but est d'accéder à cet état de l'élément contenu dans l'objet Set et non l'état de l'objet utilisé comme "requête".

"Vous n'avez pas d'autre option que d'utiliser l'itérateur" : il s'agit d'une recherche linéaire sur une collection, ce qui est totalement inefficace pour les grands ensembles (ironiquement, en interne, la fonction Set est organisé sous la forme d'une carte de hachage ou d'un arbre qui peut être interrogé efficacement). Ne le faites pas ! J'ai constaté de graves problèmes de performance dans des systèmes réels en utilisant cette approche. À mon avis, ce qui est terrible à propos de l'absence de get() n'est pas tant qu'il est un peu lourd de le contourner, mais que la plupart des programmeurs utiliseront l'approche de la recherche linéaire sans penser aux implications.

31 votes

Pas de quoi s'inquiéter. Le problème est de surcharger l'implémentation de equals pour que des objets non égaux soient "égaux". Demander une méthode qui dit "obtenez-moi l'objet identique à cet objet", et ensuite s'attendre à ce qu'un objet non identique soit retourné semble fou et facile à causer des problèmes de maintenance. Comme d'autres l'ont suggéré, l'utilisation d'une carte résout tous ces problèmes et rend ce que vous faites explicite. Il est facile de comprendre que deux objets non égaux peuvent avoir la même clé dans une carte, et que le fait d'avoir la même clé montre la relation entre eux.

32 votes

Des mots forts, @David Ogren. Mauvais ? Fou ? Mais dans votre commentaire, vous utilisez les mots "identique" et "égal" comme s'ils signifiaient la même chose. Ce n'est pas le cas. En Java, l'identité est exprimée par l'opérateur "==" et l'égalité par la méthode equals(). S'ils signifiaient la même chose, il n'y aurait pas besoin d'une méthode equals(). Dans d'autres langages, cela peut bien sûr être différent. Par exemple, en Groovy, l'identité est la méthode is(), et l'égalité est "==". Amusant, n'est-ce pas ?

17 votes

Votre critique de mon utilisation du mot identique alors que j'aurais dû utiliser le mot équivalent est tout à fait valable. Mais définir des égalités sur un objet de sorte que Foo et Bar soient "égaux" mais pas "suffisamment égaux" pour qu'il puisse les utiliser de manière équivalente va créer toutes sortes de problèmes, tant au niveau de la fonctionnalité que de la lisibilité et de la maintenabilité. Ce problème avec Set n'est que la partie émergée de l'iceberg des problèmes potentiels. Par exemple, des objets égaux doivent avoir des codes de hachage égaux. Il va donc y avoir des collisions potentielles de hachage. Est-il fou d'appeler .get(foo) spécifiquement pour obtenir quelque chose d'autre que foo ?

142voto

dacwe Points 26160

Il ne servirait à rien d'obtenir l'élément s'il est égal. A Map est mieux adapté à ce cas d'utilisation.


Si vous souhaitez toujours trouver l'élément, vous n'avez pas d'autre choix que d'utiliser l'itérateur :

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() { 
        return string.hashCode(); 
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}

275 votes

Il pourrait tout à fait y avoir un intérêt à obtenir l'élément. Qu'en est-il si vous souhaitez mettre à jour certaines valeurs de l'élément après qu'il a été ajouté à l'ensemble ? Par exemple, lorsque .equals() n'utilise pas tous les champs, comme l'a spécifié le PO. Une solution moins efficace consisterait à supprimer l'élément et à le réintroduire avec ses valeurs mises à jour.

16 votes

Je continue de penser qu'un Map est mieux adaptée ( Map<Foo, Foo> dans le cas présent).

1 votes

J'aime bien cette explication... pour une raison ou une autre, je n'ai jamais pensé à utiliser une carte de ce type.

6voto

Xymon Points 63
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

Si vous n'effectuez qu'une seule récupération, cela ne sera pas très performant car vous passerez en boucle sur tous vos éléments, mais si vous effectuez plusieurs récupérations sur un grand ensemble, vous remarquerez la différence.

2voto

Lukas Z. Points 16

Je sais que cette question a déjà été posée et qu'une réponse y a été apportée il y a longtemps, mais si quelqu'un est intéressé, voici ma solution : une classe de set personnalisée soutenue par une HashMap :

http://pastebin.com/Qv6S91n9

Vous pouvez facilement mettre en œuvre toutes les autres méthodes Set.

8 votes

Il est préférable d'inclure l'exemple plutôt que de se contenter d'un lien.

0 votes

@Lukas Z. your add() ne garantit pas que l'élément existe ou non dans le "set". Les doublons sont donc autorisés en fonction de votre implémentation ! ce qui constitue une violation flagrante du principe du set.

-2voto

Chris Willmore Points 31

Une méthode d'aide rapide qui pourrait répondre à cette situation :

<T> T onlyItem(Collection<T> items) {
    if (items.size() != 1)
        throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());

    return items.iterator().next();
}

10 votes

Il est très étrange que cette réponse ait reçu autant de votes positifs puisqu'elle ne répond pas à la question et ne tente même pas d'y répondre de quelque manière que ce soit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X