Pourquoi ne Set
fournit pas une opération pour obtenir un élément qui est é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
, alors pourquoi ne puis-je pas obtenir cet élément? :(
Pour clarifier, la equals
méthode est remplacée, mais elle vérifie uniquement l'un des champs, pas tous. Donc, deux Foo
objets considérés comme égaux peuvent avoir des valeurs différentes, c'est pourquoi je ne peux pas simplement utiliser foo
.
java
collections
set
equals
foobar
la source
la source
SortedSet
et ses implémentations, qui sont basées sur une carte (par exemple,TreeSet
permet d'accéderfirst()
).NSSet
) a une telle méthode. Il est appelémember
et il renvoie l'objet dans l'ensemble qui se compare "égal" au paramètre de lamember
méthode (qui peut bien sûr être un objet différent et également avoir des propriétés différentes, cet égal peut ne pas vérifier).Réponses:
Il serait inutile d'obtenir l'élément s'il est égal. A
Map
est mieux adapté à ce cas d'utilisation.Si vous voulez toujours trouver l'élément, vous n'avez pas d'autre option que d'utiliser l'itérateur:
la source
Map
est mieux adapté (Map<Foo, Foo>
dans ce cas.)Map<Foo, Foo>
comme remplacement, l'inconvénient est qu'une carte doit toujours stocker au moins une clé et une valeur (et pour les performances, elle doit également stocker le hachage), tandis qu'un ensemble peut s'en tirer en stockant simplement la valeur (et peut-être du hachage pour les performances). Une bonne mise en œuvre peut donc être tout aussi rapide,Map<Foo, Foo>
mais utiliser jusqu'à 50% de mémoire en moins. Dans le cas de Java, cela n'aura pas d'importance, car le HashSet est de toute façon basé sur HashMap.Pour répondre à la question précise " Pourquoi ne
Set
fournit pas une opération pour obtenir un élément égal à un autre élément?", La réponse serait: parce que les concepteurs du framework de collection n'étaient pas très tournés vers l'avenir. Ils n'ont pas anticipé votre cas d'utilisation très légitime, ont naïvement essayé de "modéliser l'abstraction de l'ensemble mathématique" (à partir du javadoc) et ont simplement oublié d'ajouter laget()
méthode utile .Passons maintenant à la question implicite " comment obtenez-vous l'élément alors": je pense que la meilleure solution est d'utiliser un
Map<E,E>
au lieu d'unSet<E>
, pour mapper les éléments à eux-mêmes. De cette façon, vous pouvez récupérer efficacement un élément de "set", car la méthode get () de l 'Map
trouvera l'élément en utilisant une table de hachage efficace ou un algorithme d'arbre. Si vous le souhaitez, vous pouvez écrire votre propre implémentation deSet
cette offre deget()
méthode supplémentaire , en encapsulant leMap
.Les réponses suivantes sont à mon avis mauvaises ou fausses:
"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é d'objet. Le but est d'avoir accès à cet état de l'élément contenu dans le
Set
, pas à l'état de l'objet utilisé comme "requête"."Vous n'avez pas d'autre option que d'utiliser l'itérateur": c'est une recherche linéaire sur une collection qui est totalement inefficace pour les grands ensembles (ironiquement, en interne, elle
Set
est organisée comme une carte de hachage ou un arbre qui pourrait être interrogé efficacement). Ne fais pas ça! J'ai vu de graves problèmes de performances dans des systèmes réels en utilisant cette approche. À mon avis, ce qui est terrible à propos de laget()
méthode manquante n'est pas tant qu'il est un peu lourd à contourner, mais que la plupart des programmeurs utiliseront l'approche de recherche linéaire sans penser aux implications.la source
get()
. Dans votre exemple, je serais très confus par customerSet.get (thisCustomer). (Alors que, une carte, comme le suggèrent de nombreuses réponses) serait très bien avec canonicalCustomerMap.get (ce client). Je serais également d'accord avec une méthode qui est plus clairement nommée (comme la méthode membre d'Objective-C sur NSSet).Si vous avez un objet égal, pourquoi avez-vous besoin de celui de l'ensemble? S'il n'est "égal" que par une clé, un
Map
serait un meilleur choix.Quoi qu'il en soit, ce qui suit le fera:
Avec Java 8, cela peut devenir une ligne unique:
la source
Convertir l'ensemble en liste, puis utiliser la
get
méthode de listela source
Le jeu par défaut en Java n'est malheureusement pas conçu pour fournir une opération "get", comme l' explique jschreiner avec précision.
Les solutions d'utiliser un itérateur pour trouver l'élément d'intérêt (suggéré par dacwe ) ou pour supprimer l'élément et le rajouter avec ses valeurs mises à jour (suggérées par KyleM ), pourraient fonctionner, mais peuvent être très inefficaces.
Redéfinir l'implémentation d'égal à égal pour que les objets non égaux soient "égaux", comme indiqué correctement par David Ogren , peut facilement causer des problèmes de maintenance.
Et l'utilisation d'une carte comme remplacement explicite (comme suggéré par beaucoup), à mon humble avis, rend le code moins élégant.
Si le but est d'avoir accès à l'instance d'origine de l'élément contenu dans l'ensemble (j'espère avoir bien compris votre cas d'utilisation), voici une autre solution possible.
J'ai personnellement eu le même besoin lors du développement d'un jeu vidéo client-serveur avec Java. Dans mon cas, chaque client avait des copies des composants stockés sur le serveur et le problème était à chaque fois qu'un client devait modifier un objet du serveur.
Passer un objet sur Internet signifiait que le client avait de toute façon différentes instances de cet objet. Afin de faire correspondre cette instance "copiée" avec celle d'origine, j'ai décidé d'utiliser des UUID Java.
J'ai donc créé une classe abstraite UniqueItem, qui donne automatiquement un identifiant unique aléatoire à chaque instance de ses sous-classes.
Cet UUID est partagé entre le client et l'instance de serveur, de sorte qu'il pourrait être facile de les faire correspondre simplement en utilisant une carte.
Cependant, l'utilisation directe d'une carte dans un cas d'utilisation similaire n'était toujours pas élégante. Quelqu'un pourrait soutenir que l'utilisation d'une carte pourrait être plus compliquée à maintenir et à gérer.
Pour ces raisons, j'ai implémenté une bibliothèque appelée MagicSet, qui rend l'utilisation d'une carte "transparente" pour le développeur.
https://github.com/ricpacca/magicset
Comme le Java HashSet d'origine, un MagicHashSet (qui est l'une des implémentations de MagicSet fournies dans la bibliothèque) utilise un support HashMap, mais au lieu d'avoir des éléments comme clés et une valeur fictive comme valeurs, il utilise l'UUID de l'élément comme clé et l'élément lui-même comme valeur. Cela n'entraîne pas de surcharge dans l'utilisation de la mémoire par rapport à un HashSet normal.
De plus, un MagicSet peut être utilisé exactement comme un ensemble, mais avec d'autres méthodes fournissant des fonctionnalités supplémentaires, comme getFromId (), popFromId (), removeFromId (), etc.
La seule condition requise pour l'utiliser est que tout élément que vous souhaitez stocker dans un MagicSet doit étendre la classe abstraite UniqueItem.
Voici un exemple de code, imaginant de récupérer l'instance d'origine d'une ville à partir d'un MagicSet, étant donné une autre instance de cette ville avec le même UUID (ou même juste son UUID).
la source
Si votre ensemble est en fait un
NavigableSet<Foo>
(comme unTreeSet
), etFoo implements Comparable<Foo>
, vous pouvez utiliser(Merci au commentaire de @ eliran-malka pour l'astuce.)
la source
Avec Java 8, vous pouvez faire:
Mais attention, .get () lève une NoSuchElementException, ou vous pouvez manipuler un élément facultatif.
la source
item->item.equals(theItemYouAreLookingFor)
peut être raccourci àtheItemYouAreLookingFor::equals
Si vous n'en obtenez qu'une seule, cela ne sera pas très performant car vous bouclerez sur tous vos éléments mais lorsque vous effectuez plusieurs récupérations sur un grand ensemble, vous remarquerez la différence.
la source
Pourquoi:
Il semble que Set joue un rôle utile en fournissant un moyen de comparaison. Il est conçu pour ne pas stocker les éléments en double.
En raison de cette intention / conception, si l'on devait obtenir () une référence à l'objet stocké, puis le muter, il est possible que les intentions de conception de Set soient contrecarrées et provoquent un comportement inattendu.
Depuis les JavaDocs
Comment:
Maintenant que les flux ont été introduits, on peut faire ce qui suit
la source
Qu'en est-il de l'utilisation de la classe Arrays?
sortie:
éléments un, deux
la source
Vous feriez mieux d'utiliser l'objet Java HashMap à cet effet http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
la source
Je sais, cela a été demandé et répondu il y a longtemps, mais si quelqu'un est intéressé, voici ma solution - classe de jeu personnalisé soutenue par HashMap:
http://pastebin.com/Qv6S91n9
Vous pouvez facilement implémenter toutes les autres méthodes Set.
la source
J'y ai fait ça !! Si vous utilisez Guava, un moyen rapide de le convertir en carte est:
la source
vous pouvez utiliser la classe Iterator
la source
Si vous voulez nième élément de HashSet, vous pouvez aller avec la solution ci-dessous, ici j'ai ajouté un objet de ModelClass dans HashSet.
la source
Si vous regardez les premières lignes de la mise en œuvre de
java.util.HashSet
vous verrez:HashSet
Utilise doncHashMap
interally de toute façon, ce qui signifie que si vous utilisez simplement unHashMap
directement et utilisez la même valeur que la clé et la valeur, vous obtiendrez l'effet que vous voulez et économisez de la mémoire.la source
il semble que l'objet approprié à utiliser soit l' Interner de la goyave:
Il a également quelques leviers très intéressants, comme concurrencyLevel, ou le type de références utilisées (il convient de noter qu'il n'offre pas un SoftInterner que je pourrais voir comme plus utile qu'un WeakInterner).
la source
Parce que toute implémentation particulière de Set peut ou non être un accès aléatoire .
Vous pouvez toujours obtenir un itérateur et parcourir l'ensemble, en utilisant la
next()
méthode des itérateurs pour retourner le résultat souhaité une fois que vous avez trouvé l'élément égal. Cela fonctionne quelle que soit la mise en œuvre. Si l'implémentation n'est PAS un accès aléatoire (imaginez un ensemble soutenu par une liste liée), uneget(E element)
méthode dans l'interface serait trompeuse, car elle devrait itérer la collection pour trouver l'élément à renvoyer, et unget(E element)
semblerait impliquer que ce serait nécessaire, que l'ensemble pourrait sauter directement à l'élément à obtenir.contains()
peut ou peut ne pas avoir à faire la même chose, bien sûr, en fonction de la mise en œuvre, mais le nom ne semble pas se prêter au même genre de malentendus.la source
Oui, utilisez
HashMap
... mais d'une manière spécialisée: le piège que je prévois en essayant d'utiliser unHashMap
comme pseudo-Set
est la confusion possible entre les éléments "réels" des élémentsMap/Set
et les "candidats", c'est-à-dire les éléments utilisés pour tester si unequal
l'élément est déjà présent. C'est loin d'être infaillible, mais vous éloigne du piège:Ensuite, faites ceci:
Mais ... vous voulez maintenant que l'
candidate
autodestruction soit en quelque sorte à moins que le programmeur ne le place immédiatement dans leMap/Set
... vous voudriezcontains
"corrompre" lecandidate
afin que toute utilisation à moins qu'il ne le rejoigne leMap
rende "anathème" ". Vous pourriez peut-être faireSomeClass
implémenter une nouvelleTaintable
interface.Une solution plus satisfaisante est un GettableSet , comme ci-dessous. Cependant, pour que cela fonctionne, vous devez être en charge de la conception
SomeClass
afin de rendre tous les constructeurs non visibles (ou ... capables et désireux de concevoir et d'utiliser une classe wrapper pour cela):La mise en oeuvre:
Vos
NoVisibleConstructor
cours ressemblent alors à ceci:PS un problème technique avec une telle
NoVisibleConstructor
classe: on peut objecter qu'une telle classe est intrinsèquementfinal
, ce qui peut être indésirable. En fait, vous pouvez toujours ajouter unprotected
constructeur sans paramètre factice :... ce qui laisserait au moins une sous-classe se compiler. Vous devriez alors vous demander si vous devez inclure une autre
getOrCreate()
méthode d'usine dans la sous-classe.L'étape finale est une classe de base abstraite (NB "élément" pour une liste, "membre" pour un ensemble) comme celle-ci pour les membres de votre ensemble (si possible - encore une fois, possibilité d'utiliser une classe wrapper où la classe n'est pas sous votre contrôle, ou a déjà une classe de base, etc.), pour un masquage maximal de l'implémentation:
... l' utilisation est assez évident (dans votre
SomeClass
destatic
méthode d'usine):la source
Le contrat du code de hachage indique clairement que:
Donc, votre hypothèse:
a tort et vous rompez le contrat. Si nous regardons la méthode "contient" de l'interface Set, nous avons ceci:
Pour accomplir ce que vous voulez, vous pouvez utiliser une carte dans laquelle vous définissez la clé et stockez votre élément avec la clé qui définit comment les objets sont différents ou égaux les uns aux autres.
la source
Méthode d'assistance rapide qui pourrait résoudre cette situation:
la source
Suivre peut être une approche
la source
Essayez d'utiliser un tableau:
la source