Obtenir un élément d'un ensemble

324

Pourquoi ne Setfournit 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 Setcontient un élément égal à bar, alors pourquoi ne puis-je pas obtenir cet élément? :(

Pour clarifier, la equalsméthode est remplacée, mais elle vérifie uniquement l'un des champs, pas tous. Donc, deux Fooobjets considérés comme égaux peuvent avoir des valeurs différentes, c'est pourquoi je ne peux pas simplement utiliser foo.

foobar
la source
2
Ce message est déjà largement discuté et de bonnes réponses ont été suggérées. Cependant, si vous recherchez simplement un ensemble ordonné, utilisez simplement SortedSetet ses implémentations, qui sont basées sur une carte (par exemple, TreeSetpermet d'accéder first()).
Eliran Malka
3
Cette méthode me manque également pour exactement le même cas que vous avez décrit ci-dessus. Objective-C ( NSSet) a une telle méthode. Il est appelé memberet il renvoie l'objet dans l'ensemble qui se compare "égal" au paramètre de la membermé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).
Mecki

Réponses:

118

Il serait inutile d'obtenir l'élément s'il est égal. A Mapest 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:

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);
    }
}
dacwe
la source
234
Il pourrait absolument être utile d'obtenir l'élément. Que faire si vous souhaitez mettre à jour certaines des valeurs de l'élément après qu'il a déjà été ajouté à l'ensemble? Par exemple, lorsque .equals () n'utilise pas tous les champs, comme l'OP spécifié. Une solution moins efficace serait de supprimer l'élément et de le rajouter avec ses valeurs mises à jour.
KyleM
14
Je dirais toujours qu'un Mapest mieux adapté ( Map<Foo, Foo>dans ce cas.)
dacwe
22
@dacwe, je suis arrivé ici parce que j'ai commencé à chercher un moyen d'éviter exactement cela! Un objet qui agit à la fois comme clé et valeur correspondante est exactement ce que devrait être un ensemble. Dans mon cas, je voudrais obtenir un objet complexe à partir d'un ensemble par clé (chaîne). Cette chaîne est encapsulée (et unique) à l'objet mappé. En fait, tout l'objet «tourne» autour de ladite clé. De plus, l'appelant connaît ladite chaîne, mais pas l'objet lui-même; c'est exactement pourquoi il veut le récupérer par clé. J'utilise une carte maintenant bien sûr, mais cela reste un comportement étrange.
pauluss86
4
@KyleM Je comprends le cas d'utilisation, mais je tiens à souligner l'importance de ne pas toucher aux attributs qui font partie de hashCode / equals. À partir de l'ensemble Javadoc: "Remarque: Il faut être très prudent si des objets modifiables sont utilisés comme éléments d'ensemble. Le comportement d'un ensemble n'est pas spécifié si la valeur d'un objet est modifiée d'une manière qui affecte des comparaisons égales tandis que l'objet est un élément dans l'ensemble. " - Je recommande que ces objets soient immuables, ou du moins aient des attributs de clé immuables.
stivlo
5
Je suis d'accord que vous pouvez utiliser 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.
Mecki
372

Pour répondre à la question précise " Pourquoi ne Setfournit 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 la get()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'un Set<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 ' Maptrouvera 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 de Setcette offre de get()méthode supplémentaire , en encapsulant le Map.

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 Setest 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 la get()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.

jschreiner
la source
27
meh. Remplacer l'implémentation d'égaux pour que les objets non égaux soient «égaux» est le problème ici. Demander une méthode qui dit "obtenez-moi l'objet identique à cet objet", puis attendez-vous à ce qu'un objet non identique soit retourné semble fou et facile à provoquer des problèmes de maintenance. Comme d'autres l'ont suggéré, l'utilisation d'une carte résout tous ces problèmes: et cela explique ce que vous faites. Il est facile de comprendre que deux objets non égaux peuvent avoir la même clé dans une carte, et avoir la même clé montrerait la relation entre eux.
David Ogren
20
Des mots forts, @David Ogren. Meh? Fou? Mais dans votre commentaire, vous utilisez les mots "identiques" et "égaux" comme s'ils signifiaient la même chose. Ils ne. Plus précisément, en Java, l'identité est exprimée par l'opérateur "==" et l'égalité est exprimée par la méthode equals (). S'ils signifiaient la même chose, il n'y aurait pas du tout besoin d'une méthode equals (). Dans d'autres langues, cela peut bien sûr être différent. Par exemple, dans Groovy, l'identité est la méthode is () et l'égalité est "==". C'est drôle, non?
jschreiner
15
Votre critique de mon utilisation du mot identique alors que j'aurais dû utiliser le mot équivalent est très valable. Mais définir des égaux sur un objet afin que Foo et Bar soient "égaux" mais pas "suffisamment égaux" pour qu'il les utilise de manière équivalente va créer toutes sortes de problèmes à la fois avec la fonctionnalité et avec la lisibilité / maintenabilité. Ce problème avec Set n'est que la pointe de l'iceberg pour les problèmes potentiels. Par exemple, des objets égaux doivent avoir des codes de hachage égaux. Il va donc avoir des collisions de hachage potentielles. Est-ce fou de s'opposer à appeler .get (foo) spécifiquement pour obtenir autre chose que foo?
David Ogren
12
Il est probablement intéressant de noter que, par exemple, HashSet est implémenté comme un wrapper autour d'un HashMap (qui mappe les clés à une valeur fictive). Ainsi, l'utilisation explicite d'un HashMap au lieu d'un HashSet n'entraînerait pas de surcharge dans l'utilisation de la mémoire.
Alexey B.19
4
@ user686249 J'ai l'impression que cela s'est transformé en un simple débat académique. Je reconnais que j'aurais peut-être été exagéré en m'opposant à la substitution d'égaux. Surtout dans une utilisation comme la vôtre. Mais, j'ai toujours une objection à l'idée d'appeler cette méthode 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).
David Ogren
19

Si vous avez un objet égal, pourquoi avez-vous besoin de celui de l'ensemble? S'il n'est "égal" que par une clé, unMap serait un meilleur choix.

Quoi qu'il en soit, ce qui suit le fera:

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

Avec Java 8, cela peut devenir une ligne unique:

return all.stream().filter(sample::equals).findAny().orElse(null);
Arne Burmeister
la source
J'aime mieux cette réponse, j'éviterais simplement d'utiliser deux déclarations de retour, car c'est contre la POO et cela rend la valeur de complexité cyclomatique plus élevée.
Leo
8
@Leo merci, mais le paradigme de sortie unique n'est pas contre la POO et est surtout invalide pour les langues plus modernes que Fortran ou COBOL, voir aussi softwareengineering.stackexchange.com/questions/118703/…
Arne Burmeister
1
Utiliser une carte au lieu d'un ensemble semble être une meilleure option: itérer sur les éléments d'un ensemble est plus de travail que d'obtenir une seule valeur d'une carte. (O (N) vs O (1))
Jamie Flournoy
@JamieFlournoy à droite, si vous devez vérifier plusieurs fois le même ensemble pour différents éléments, c'est bien mieux. Pour un usage unique non, car cela nécessite plus d'efforts pour construire la carte en premier.
Arne Burmeister
18

Convertir l'ensemble en liste, puis utiliser la getméthode de liste

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
À Kra
la source
37
Je ne comprends pas. Cela récupérera un objet arbitraire de l'ensemble. Pas l' objet.
aioobe
14

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

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}
ricpacca
la source
11

Si votre ensemble est en fait un NavigableSet<Foo>(comme un TreeSet), et Foo implements Comparable<Foo>, vous pouvez utiliser

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(Merci au commentaire de @ eliran-malka pour l'astuce.)

Jesse Glick
la source
5
Si cela ne me dérangeait pas que quelqu'un lise mon code ayant la pensée initiale que j'étais devenu complètement fou, ce serait une excellente solution.
Adam
10

Avec Java 8, vous pouvez faire:

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

Mais attention, .get () lève une NoSuchElementException, ou vous pouvez manipuler un élément facultatif.

temps nuageux
la source
5
item->item.equals(theItemYouAreLookingFor)peut être raccourci àtheItemYouAreLookingFor::equals
Henno Vermeulen
5
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'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.

Xymon
la source
5

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

Il faut être très prudent si des objets mutables sont utilisés comme éléments fixes. Le comportement d'un ensemble n'est pas spécifié si la valeur d'un objet est modifiée d'une manière qui affecte des comparaisons égales alors que l'objet est un élément de l'ensemble.

Comment:

Maintenant que les flux ont été introduits, on peut faire ce qui suit

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Jason M
la source
2

Qu'en est-il de l'utilisation de la classe Arrays?

import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;

public class MyClass {
    public static void main(String args[]) {
        Set mySet = new HashSet();
        mySet.add("one");
        mySet.add("two");
        List list = Arrays.asList(mySet.toArray());
        Object o0 = list.get(0);
        Object o1 = list.get(1);
        System.out.println("items " + o0+","+o1);
    }
}

sortie:
éléments un, deux

rapidité
la source
1

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.

Lukas Z.
la source
7
Il est préférable d'inclure l'exemple au lieu de simplement le lier à un.
Tous les travailleurs sont essentiels le
1

J'y ai fait ça !! Si vous utilisez Guava, un moyen rapide de le convertir en carte est:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
Heisenberg
la source
1

vous pouvez utiliser la classe Iterator

import java.util.Iterator;
import java.util.HashSet;

public class MyClass {
 public static void main(String[ ] args) {
 HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");

Iterator<String> it = animals.iterator();
while(it.hasNext()) {
  String value = it.next();
  System.out.println(value);   
 }
 }
}
chiha asma
la source
1

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.

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}
Hardik Patel
la source
1

Si vous regardez les premières lignes de la mise en œuvre de java.util.HashSetvous verrez:

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

HashSetUtilise donc HashMapinterally de toute façon, ce qui signifie que si vous utilisez simplement un HashMapdirectement 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.

rghome
la source
1

il semble que l'objet approprié à utiliser soit l' Interner de la goyave:

Fournit un comportement équivalent à String.intern () pour les autres types immuables. Les implémentations courantes sont disponibles dans la classe Interners .

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

molyss
la source
0

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), une get(E element)méthode dans l'interface serait trompeuse, car elle devrait itérer la collection pour trouver l'élément à renvoyer, et un get(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.

Tom Tresansky
la source
2
Tout ce que la méthode get () ferait est déjà fait par la méthode contains (). Vous ne pouvez pas implémenter contains () sans obtenir l'objet contenu et appeler sa méthode .equals (). Les concepteurs d'API ne semblaient pas hésiter à ajouter un get () à java.util.List, même si cela serait lent dans certaines implémentations.
Bryan Rink du
Je ne pense pas que ce soit vrai. Deux objets peuvent être égaux via égaux, mais pas identiques via ==. Si vous aviez l'objet A et un ensemble S contenant l'objet B et A. égal (B) mais A! = B et que vous vouliez obtenir une référence à B, vous pourriez appeler S.get (A) pour obtenir une référence à B, en supposant que vous disposiez d'une méthode get avec la sémantique de la méthode get de List, ce qui est un cas d'utilisation différent de celui de vérifier si S contient (A) (ce qui serait le cas). Ce n'est même pas si rare un cas d'utilisation pour les collections.
Tom Tresansky
0

Oui, utilisez HashMap... mais d'une manière spécialisée: le piège que je prévois en essayant d'utiliser un HashMapcomme pseudo- Setest la confusion possible entre les éléments "réels" des éléments Map/Setet les "candidats", c'est-à-dire les éléments utilisés pour tester si un equall'élément est déjà présent. C'est loin d'être infaillible, mais vous éloigne du piège:

class SelfMappingHashMap<V> extends HashMap<V, V>{
    @Override
    public String toString(){
        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
        return keySet().toString();
    }

    @Override
    public V get( Object key ){
        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
    }

    @Override
    public V put( V key, V value ){
       // thorny issue here: if you were indavertently to `put`
       // a "candidate instance" with the element already in the `Map/Set`: 
       // these will obviously be considered equivalent 
       assert key.equals( value );
       return super.put( key, value );
    }

    public V tryToGetRealFromCandidate( V key ){
        return super.get(key);
    }
}

Ensuite, faites ceci:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}

Mais ... vous voulez maintenant que l' candidateautodestruction soit en quelque sorte à moins que le programmeur ne le place immédiatement dans le Map/Set... vous voudriez contains"corrompre" le candidateafin que toute utilisation à moins qu'il ne le rejoigne le Maprende "anathème" ". Vous pourriez peut-être faire SomeClassimplémenter une nouvelle Taintableinterface.

Une solution plus satisfaisante est un GettableSet , comme ci-dessous. Cependant, pour que cela fonctionne, vous devez être en charge de la conception SomeClassafin de rendre tous les constructeurs non visibles (ou ... capables et désireux de concevoir et d'utiliser une classe wrapper pour cela):

public interface NoVisibleConstructor {
    // again, this is a "nudge" technique, in the sense that there is no known method of 
    // making an interface enforce "no visible constructor" in its implementing classes 
    // - of course when Java finally implements full multiple inheritance some reflection 
    // technique might be used...
    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};

public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
    V getGenuineFromImpostor( V impostor ); // see below for naming
}

La mise en oeuvre:

public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
    private Map<V, V> map = new HashMap<V, V>();

    @Override
    public V getGenuineFromImpostor(V impostor ) {
        return map.get( impostor );
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey( o );
    }

    @Override
    public boolean add(V e) {
        assert e != null;
        V result = map.put( e,  e );
        return result != null;
    }

    @Override
    public boolean remove(Object o) {
        V result = map.remove( o );
        return result != null;
    }

    @Override
    public boolean addAll(Collection<? extends V> c) {
        // for example:
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        map.clear();
    }

    // implement the other methods from Set ...
}

Vos NoVisibleConstructorcours ressemblent alors à ceci:

class SomeClass implements NoVisibleConstructor {

    private SomeClass( Object param1, Object param2 ){
        // ...
    }

    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
        SomeClass candidate = new SomeClass( param1, param2 );
        if (gettableSet.contains(candidate)) {
            // obviously this then means that the candidate "fails" (or is revealed
            // to be an "impostor" if you will).  Return the existing element:
            return gettableSet.getGenuineFromImpostor(candidate);
        }
        gettableSet.add( candidate );
        return candidate;
    }

    @Override
    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
       // more elegant implementation-hiding: see below
    }
}

PS un problème technique avec une telle NoVisibleConstructorclasse: on peut objecter qu'une telle classe est intrinsèquement final, ce qui peut être indésirable. En fait, vous pouvez toujours ajouter un protectedconstructeur sans paramètre factice :

protected SomeClass(){
    throw new UnsupportedOperationException();
}

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

public abstract class AbstractSetMember implements NoVisibleConstructor {
    @Override
    public NoVisibleConstructor
            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
        AbstractSetMember member = this;
        @SuppressWarnings("unchecked") // unavoidable!
        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
        if (gettableSet.contains( member )) {
            member = set.getGenuineFromImpostor( member );
            cleanUpAfterFindingGenuine( set );
        } else {
            addNewToSet( set );
        }
        return member;
    }

    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}

... l' utilisation est assez évident (dans votre SomeClassde staticméthode d'usine):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
Mike rongeur
la source
0

Le contrat du code de hachage indique clairement que:

"Si deux objets sont égaux selon la méthode Object, alors appeler la méthode hashCode sur chacun des deux objets doit produire le même résultat entier."

Donc, votre hypothèse:

"Pour clarifier, la méthode equals est remplacée, mais elle ne vérifie qu'un des champs, pas tous. Donc, deux objets Foo qui sont considérés comme égaux peuvent en fait avoir des valeurs différentes, c'est pourquoi je ne peux pas simplement utiliser foo."

a tort et vous rompez le contrat. Si nous regardons la méthode "contient" de l'interface Set, nous avons ceci:

boolean contient (Object o);
Renvoie true si cet ensemble contient l'élément spécifié. Plus formellement, retourne vrai si et seulement si cet ensemble contient un élément "e" tel que o == null? e == null: o.equals (e)

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.

jfajunior
la source
-2

Méthode d'assistance rapide qui pourrait résoudre 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();
}
Chris Willmore
la source
8
Il est très étrange que cette réponse ait obtenu autant de votes positifs car elle ne répond pas à la question ni même tente de la résoudre de quelque manière que ce soit.
David Conrad
-2

Suivre peut être une approche

   SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
   Set<String> main = se_get.getStringSet("mydata",null);
   for(int jk = 0 ; jk < main.size();jk++)
   {
      Log.i("data",String.valueOf(main.toArray()[jk]));
   }
Vikrant
la source
-2

Essayez d'utiliser un tableau:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
canni
la source