Ein Element aus einem Set abrufen

322

Warum wird keine SetOperation bereitgestellt, um ein Element abzurufen, das einem anderen Element entspricht?

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

Ich kann fragen, ob das Setein Element enthält, das gleich barist. Warum kann ich dieses Element nicht erhalten? :((

Zur Verdeutlichung wird die equalsMethode überschrieben, überprüft jedoch nur eines der Felder, nicht alle. Zwei FooObjekte, die als gleich angesehen werden, können tatsächlich unterschiedliche Werte haben. Deshalb kann ich sie nicht einfach verwenden foo.

foobar
quelle
2
Dieser Beitrag wird bereits ausführlich diskutiert und es wurden gute Antworten vorgeschlagen. Wenn Sie jedoch nur nach einem geordneten Satz suchen, verwenden Sie einfach SortedSetund dessen Implementierungen, die kartenbasiert sind (z. B. TreeSetZugriff ermöglichen first()).
Eliran Malka
3
Ich vermisse diese Methode auch für genau den gleichen Fall, den Sie oben beschrieben haben. Objective-C ( NSSet) hat eine solche Methode. Es wird aufgerufen memberund gibt das Objekt innerhalb der Menge zurück, das "gleich" mit dem Parameter der memberMethode vergleicht (was natürlich ein anderes Objekt sein kann und auch andere Eigenschaften hat, die gleich möglicherweise nicht prüfen).
Mecki

Antworten:

118

Es wäre sinnlos, das Element zu erhalten, wenn es gleich ist. A Mapist für diesen Fall besser geeignet.


Wenn Sie das Element dennoch finden möchten, haben Sie keine andere Möglichkeit, als den Iterator zu verwenden:

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
quelle
234
Es könnte durchaus sinnvoll sein, das Element zu erhalten. Was ist, wenn Sie einige Werte des Elements aktualisieren möchten, nachdem es bereits zum Set hinzugefügt wurde? Zum Beispiel, wenn .equals () nicht alle Felder verwendet, wie im OP angegeben. Eine weniger effiziente Lösung wäre, das Element zu entfernen und es mit aktualisierten Werten erneut hinzuzufügen.
KyleM
14
Ich würde immer noch argumentieren, dass a Mapbesser geeignet ist ( Map<Foo, Foo>in diesem Fall)
dacwe
22
@dacwe, ich bin hierher gekommen, weil ich nach einem Weg gesucht habe, genau das zu vermeiden! Ein Objekt, das gleichzeitig als Schlüssel und entsprechender Wert fungiert, ist genau das, worum es bei einer Menge gehen sollte. In meinem Fall möchte ich ein komplexes Objekt aus einem Set by Key (String) erhalten. Diese Zeichenfolge ist für das zuzuordnende Objekt gekapselt (und eindeutig). Tatsächlich dreht sich das gesamte Objekt um diesen Schlüssel. Darüber hinaus kennt der Aufrufer den besagten String, aber nicht das Objekt selbst; Genau deshalb möchte es per Schlüssel abgerufen werden. Ich benutze jetzt natürlich eine Karte, aber es bleibt seltsames Verhalten.
Pauluss86
4
@KyleM Ich verstehe den Anwendungsfall, möchte aber betonen, wie wichtig es ist, Attribute, die Teil von hashCode / equals sind, nicht zu berühren. Aus dem Set Javadoc: "Hinweis: Wenn veränderbare Objekte als Set-Elemente verwendet werden, ist große Vorsicht geboten. Das Verhalten eines Sets wird nicht angegeben, wenn der Wert eines Objekts so geändert wird, dass Vergleiche gleich sind, während das Objekt ein Objekt ist Element in der Menge. " - Ich empfehle, dass diese Objekte unveränderlich sind oder zumindest unveränderliche Schlüsselattribute haben.
Stivlo
5
Ich bin damit einverstanden, dass Sie Map<Foo, Foo>als Ersatz verwenden können. Der Nachteil ist, dass eine Karte immer mindestens einen Schlüssel und einen Wert speichern muss (und für die Leistung sollte sie auch den Hash speichern), während ein Satz davonkommen kann, nur den Wert zu speichern (und vielleicht Hash für Leistung). Eine gute Set-Implementierung kann also genauso schnell sein, Map<Foo, Foo>aber bis zu 50% weniger Speicher verbrauchen. Im Falle von Java spielt es keine Rolle, da das HashSet ohnehin intern auf HashMap basiert.
Mecki
371

Um die genaue Frage zu beantworten: " Warum wird keine SetOperation bereitgestellt, um ein Element zu erhalten, das einem anderen Element entspricht?", Lautete die Antwort: Weil die Designer des Sammlungsframeworks nicht sehr vorausschauend waren. Sie haben Ihren sehr legitimen Anwendungsfall nicht vorweggenommen, naiv versucht, "die mathematische Mengenabstraktion zu modellieren" (aus dem Javadoc) und einfach vergessen, die nützliche get()Methode hinzuzufügen .

Nun zur impliziten Frage " Wie bekommt man das Element dann?": Ich denke, die beste Lösung besteht darin, a Map<E,E>anstelle von a zu verwenden Set<E>, um die Elemente sich selbst zuzuordnen. Auf diese Weise können Sie ein Element effizient aus der "Menge" abrufen, da die get () -Methode von Mapdas Element mithilfe einer effizienten Hash-Tabelle oder eines Baum-Algorithmus findet. Wenn Sie möchten, können Sie Ihre eigene Implementierung schreiben Set, die die zusätzliche get()Methode bietet, die die Map.

Die folgenden Antworten sind meiner Meinung nach schlecht oder falsch:

"Sie müssen das Element nicht erhalten, weil Sie bereits ein gleiches Objekt haben": Die Behauptung ist falsch, wie Sie bereits in der Frage gezeigt haben. Zwei Objekte, die noch gleich sind, können unterschiedliche Zustände haben, die für die Objektgleichheit nicht relevant sind. Das Ziel ist es, Zugriff auf diesen Status des Elements zu erhalten, das in dem enthalten ist Set, nicht auf den Status des Objekts, das als "Abfrage" verwendet wird.

"Sie haben keine andere Wahl, als den Iterator zu verwenden": Dies ist eine lineare Suche über eine Sammlung, die für große Mengen völlig ineffizient ist (ironischerweise ist sie intern Setals Hash-Map oder Baum organisiert, der effizient abgefragt werden kann). Tu es nicht! Ich habe bei Verwendung dieses Ansatzes schwerwiegende Leistungsprobleme in realen Systemen festgestellt. Meiner Meinung nach ist das Schreckliche an der fehlenden get()Methode nicht so sehr, dass es etwas umständlich ist, sie zu umgehen, sondern dass die meisten Programmierer den linearen Suchansatz verwenden, ohne an die Auswirkungen zu denken.

jschreiner
quelle
27
meh. Das Problem besteht darin, die Implementierung von equals so zu überschreiben, dass ungleiche Objekte "gleich" sind. Es scheint verrückt und einfach zu sein, nach einer Methode zu fragen, die besagt, dass "mir das identische Objekt zu diesem Objekt gegeben wird", und dann zu erwarten, dass ein nicht identisches Objekt zurückgegeben wird. Wie andere vorgeschlagen haben, löst die Verwendung einer Karte all diese Probleme: und es macht das, was Sie tun, selbsterklärend. Es ist leicht zu verstehen, dass zwei ungleiche Objekte möglicherweise denselben Schlüssel in einer Karte haben und dass derselbe Schlüssel die Beziehung zwischen ihnen zeigt.
David Ogren
20
Starke Worte, @ David Ogren. Meh? Verrückt? Aber in Ihrem Kommentar verwenden Sie die Wörter "identisch" und "gleich", als ob sie dasselbe bedeuten würden. Sie nicht. Insbesondere in Java wird Identität durch den Operator "==" und Gleichheit durch die Methode equals () ausgedrückt. Wenn sie dasselbe bedeuten würden, wäre eine equals () -Methode überhaupt nicht erforderlich. In anderen Sprachen kann dies natürlich anders sein. In Groovy ist Identität beispielsweise die Methode is () und Gleichheit ist "==". Komisch, nicht wahr?
Jschreiner
15
Ihre Kritik an meiner Verwendung des Wortes identisch, wenn ich das Wort Äquivalent hätte verwenden sollen, ist sehr berechtigt. Die Definition von Gleichheit für ein Objekt, sodass Foo und Bar "gleich", aber nicht "gleich genug" sind, um sie gleichwertig zu verwenden, führt zu allen möglichen Problemen sowohl hinsichtlich der Funktionalität als auch hinsichtlich der Lesbarkeit / Wartbarkeit. Dieses Problem mit Set ist nur die Spitze des Eisbergs für mögliche Probleme. Beispielsweise müssen gleiche Objekte gleiche Hash-Codes haben. Also wird er potenzielle Hash-Kollisionen haben. Ist es verrückt, .get (foo) speziell zu beanstanden, um etwas anderes als foo zu bekommen?
David Ogren
12
Es ist wahrscheinlich erwähnenswert, dass HashSet beispielsweise als Wrapper um eine HashMap implementiert ist (die die Schlüssel einem Dummy-Wert zuordnet). Die explizite Verwendung einer HashMap anstelle eines HashSet würde also keinen Overhead bei der Speichernutzung verursachen.
Alexey B.
4
@ user686249 Ich habe das Gefühl, dass dies nur zu einer akademischen Debatte geworden ist. Ich gebe zu, dass ich möglicherweise über Bord gegangen bin, um Einwände gegen das Überschreiben von Gleichen zu erheben. Besonders bei einer Verwendung wie Ihrer. Ich habe jedoch immer noch Einwände gegen die Idee, diese Methode aufzurufen get(). In Ihrem Beispiel würde mich customerSet.get (thisCustomer) sehr verwirren. (Während eine Karte, wie von vielen Antworten vorgeschlagen) mit canonicalCustomerMap.get (diesem Kunden) in Ordnung wäre. Ich wäre auch mit einer Methode einverstanden, die klarer benannt ist (wie die Mitgliedsmethode von Objective-C in NSSet).
David Ogren
19

Wenn Sie ein gleiches Objekt haben, warum brauchen Sie das aus dem Set? Wenn es nur durch einen Schlüssel "gleich" ist, Mapwäre ein eine bessere Wahl.

Wie auch immer, das wird es tun:

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

Mit Java 8 kann dies ein Einzeiler werden:

return all.stream().filter(sample::equals).findAny().orElse(null);
Arne Burmeister
quelle
Ich mag diese Antwort besser, ich würde es einfach vermeiden, zwei return-Anweisungen zu verwenden, da dies gegen OOP ist und den Wert für die zyklomatische Komplexität höher macht.
Leo
8
@ Leo danke, aber Single-Exit-Paradigma ist nicht gegen OOP und ist meistens ungültig für Sprachen, die moderner als Fortran oder COBOL sind, siehe auch softwareengineering.stackexchange.com/questions/118703/…
Arne Burmeister
1
Die Verwendung einer Karte anstelle eines Satzes scheint eine bessere Option zu sein: Das Durchlaufen der Elemente eines Satzes ist mehr Arbeit als das Abrufen eines einzelnen Werts von einer Karte. (O (N) gegen O (1))
Jamie Flournoy
@JamieFlournoy richtig, wenn Sie das gleiche Set mehrmals auf verschiedene Elemente überprüfen müssen, ist das viel besser. Für den einmaligen Gebrauch nicht, da es mehr Aufwand erfordert, die Karte zuerst zu erstellen.
Arne Burmeister
18

Konvertieren Sie den Satz in eine Liste und verwenden Sie dann die getListenmethode

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
Zu Kra
quelle
37
Ich verstehe das nicht. Dadurch wird ein beliebiges Objekt der Menge abgerufen . Nicht das Objekt.
Aioobe
14

Die Standardeinstellung in Java ist leider nicht dafür ausgelegt, eine "get" -Operation bereitzustellen, wie jschreiner genau erklärt hat.

Die Lösungen, einen Iterator zu verwenden, um das interessierende Element zu finden (von dacwe vorgeschlagen ) oder das Element zu entfernen und es mit aktualisierten Werten (von KyleM vorgeschlagen ) erneut hinzuzufügen , könnten funktionieren, können jedoch sehr ineffizient sein.

Das Überschreiben der Implementierung von equals, sodass ungleiche Objekte "gleich" sind, wie von David Ogren korrekt angegeben , kann leicht zu Wartungsproblemen führen.

Und die Verwendung einer Karte als expliziten Ersatz (wie von vielen vorgeschlagen), imho, macht den Code weniger elegant.

Wenn das Ziel darin besteht, Zugriff auf die ursprüngliche Instanz des in der Menge enthaltenen Elements zu erhalten (ich hoffe, ich habe Ihren Anwendungsfall richtig verstanden), ist hier eine andere mögliche Lösung.


Ich persönlich hatte das gleiche Bedürfnis bei der Entwicklung eines Client-Server-Videospiels mit Java. In meinem Fall hatte jeder Client Kopien der auf dem Server gespeicherten Komponenten, und das Problem bestand immer dann, wenn ein Client ein Objekt des Servers ändern musste.

Das Weiterleiten eines Objekts über das Internet bedeutete, dass der Client ohnehin unterschiedliche Instanzen dieses Objekts hatte. Um diese "kopierte" Instanz mit der ursprünglichen zu vergleichen, habe ich mich für die Verwendung von Java-UUIDs entschieden.

Deshalb habe ich eine abstrakte Klasse UniqueItem erstellt, die jeder Instanz ihrer Unterklassen automatisch eine zufällige eindeutige ID zuweist.

Diese UUID wird zwischen dem Client und der Serverinstanz gemeinsam genutzt, sodass es auf diese Weise einfach sein kann, sie mithilfe einer einfachen Karte abzugleichen.

Die direkte Verwendung einer Karte in einem ähnlichen Anwendungsfall war jedoch immer noch unelegant. Jemand könnte argumentieren, dass die Verwendung einer Karte möglicherweise komplizierter zu handhaben und zu handhaben ist.

Aus diesen Gründen habe ich eine Bibliothek namens MagicSet implementiert, die die Verwendung einer Map für den Entwickler "transparent" macht.

https://github.com/ricpacca/magicset


Wie das ursprüngliche Java HashSet verwendet ein MagicHashSet (eine der in der Bibliothek bereitgestellten Implementierungen von MagicSet) eine unterstützende HashMap, verwendet jedoch anstelle von Elementen als Schlüssel und einem Dummy-Wert als Werte die UUID des Elements als Schlüssel und das Element selbst als Wert. Dies verursacht keinen Overhead bei der Speichernutzung im Vergleich zu einem normalen HashSet.

Darüber hinaus kann ein MagicSet genau als Set verwendet werden, jedoch mit einigen weiteren Methoden, die zusätzliche Funktionen bereitstellen, wie getFromId (), popFromId (), removeFromId () usw.

Die einzige Voraussetzung für die Verwendung ist, dass jedes Element, das Sie in einem MagicSet speichern möchten, die abstrakte Klasse UniqueItem erweitern muss.


Hier ist ein Codebeispiel, in dem Sie sich vorstellen, die ursprüngliche Instanz einer Stadt aus einem MagicSet abzurufen, wenn eine andere Instanz dieser Stadt dieselbe UUID (oder sogar nur ihre UUID) aufweist.

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
quelle
11

Wenn Ihr Set tatsächlich ein NavigableSet<Foo>(wie ein TreeSet) ist, und Foo implements Comparable<Foo>, können Sie verwenden

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

(Danke an @ eliran-malkas Kommentar für den Hinweis.)

Jesse Glick
quelle
5
Wenn es mir nichts ausmachen würde, wenn jemand meinen Code liest und anfänglich denkt, ich wäre völlig verrückt geworden, wäre dies eine großartige Lösung.
Adam
10

Mit Java 8 können Sie Folgendes tun:

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

Aber seien Sie vorsichtig, .get () löst eine NoSuchElementException aus, oder Sie können ein optionales Element bearbeiten.

wolkiges Wetter
quelle
5
item->item.equals(theItemYouAreLookingFor)kann verkürzt werden auftheItemYouAreLookingFor::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);

Wenn Sie nur einen Abruf durchführen, ist dies nicht sehr leistungsfähig, da Sie alle Ihre Elemente durchlaufen. Wenn Sie jedoch mehrere Abfragen an einem großen Satz durchführen, werden Sie den Unterschied bemerken.

Xymon
quelle
5

Warum:

Es scheint, dass Set eine nützliche Rolle bei der Bereitstellung eines Vergleichsmittels spielt. Es ist so konzipiert, dass keine doppelten Elemente gespeichert werden.

Aufgrund dieser Absicht / dieses Entwurfs ist es möglich, dass die Entwurfsabsichten von Set vereitelt werden und unerwartetes Verhalten verursachen, wenn man () einen Verweis auf das gespeicherte Objekt erhält und es dann mutiert.

Aus den JavaDocs

Es ist große Vorsicht geboten, wenn veränderbare Objekte als Set-Elemente verwendet werden. Das Verhalten einer Menge wird nicht angegeben, wenn der Wert eines Objekts so geändert wird, dass sich dies auf Vergleiche auswirkt, während das Objekt ein Element in der Menge ist.

Wie:

Nachdem Streams eingeführt wurden, können Sie Folgendes tun

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

Was ist mit der Arrays-Klasse?

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

Ausgabe:
Punkte eins, zwei

Geschwindigkeit
quelle
1

Ich weiß, dies wurde vor langer Zeit gefragt und beantwortet. Wenn jedoch jemand interessiert ist, ist hier meine Lösung - eine benutzerdefinierte Set-Klasse, die von HashMap unterstützt wird:

http://pastebin.com/Qv6S91n9

Sie können alle anderen Set-Methoden problemlos implementieren.

Lukas Z.
quelle
7
Es wird bevorzugt, das Beispiel einzuschließen, anstatt nur auf eines zu verlinken.
Alle Arbeiter sind wesentlich
1

Kenne ich schon!! Wenn Sie Guava verwenden, können Sie es schnell in eine Karte konvertieren:

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

Sie können die Iterator-Klasse verwenden

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
quelle
1

Wenn Sie das n-te Element von HashSet möchten, können Sie mit der folgenden Lösung fortfahren. Hier habe ich ein Objekt von ModelClass in HashSet hinzugefügt.

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
quelle
1

Wenn Sie sich die ersten Zeilen der Implementierung ansehen, werden java.util.HashSetSie sehen:

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

So HashSetVerwendungen HashMapinterally sowieso, was bedeutet , dass , wenn Sie nur verwenden , HashMapwerden Sie den Effekt , den Sie und einige Speicher speichern selbst wollen Sie direkt und verwenden den gleichen Wert wie der Schlüssel und Wert.

rghome
quelle
1

Es sieht so aus, als wäre das richtige Objekt der Interner von Guave:

Bietet ein gleichwertiges Verhalten wie String.intern () für andere unveränderliche Typen. Allgemeine Implementierungen sind in der Interners- Klasse verfügbar .

Es hat auch einige sehr interessante Hebel, wie ConcurrencyLevel oder die Art der verwendeten Referenzen (es könnte erwähnenswert sein, dass es keinen SoftInterner bietet, den ich als nützlicher als einen WeakInterner ansehen könnte).

Molyss
quelle
0

Weil eine bestimmte Implementierung von Set ein Direktzugriff sein kann oder nicht .

Sie können jederzeit einen Iterator abrufen und die Menge mit der next()Methode der Iteratoren durchlaufen , um das gewünschte Ergebnis zurückzugeben, sobald Sie das gleiche Element gefunden haben. Dies funktioniert unabhängig von der Implementierung. Wenn es sich bei der Implementierung NICHT um einen Direktzugriff handelt (stellen Sie sich ein Set mit verknüpfter Liste vor), würde eine get(E element)Methode in der Schnittstelle täuschen, da sie die Sammlung iterieren müsste, um das zurückzugebende Element zu finden, und a get(E element)scheint dies zu implizieren notwendig, dass das Set direkt zum zu springenden Element springen kann.

contains() Abhängig von der Implementierung muss möglicherweise das Gleiche getan werden oder auch nicht, aber der Name scheint sich nicht für die gleichen Missverständnisse zu eignen.

Tom Tresansky
quelle
2
Alles, was die Methode get () tun würde, wird bereits von der Methode includes () ausgeführt. Sie können includes () nicht implementieren, ohne das enthaltene Objekt abzurufen und seine .equals () -Methode aufzurufen. Die API-Designer schienen keine Bedenken zu haben, ein get () zu java.util.List hinzuzufügen, obwohl es in einigen Implementierungen langsam sein würde.
Bryan Rink
Ich denke nicht, dass das wahr ist. Zwei Objekte können über gleich gleich sein, aber über == nicht identisch. Wenn Sie Objekt A und eine Menge S mit Objekt B und A.equals (B), aber A! = B hatten und eine Referenz auf B erhalten möchten, können Sie S.get (A) aufrufen, um eine Referenz auf zu erhalten B, vorausgesetzt, Sie hatten eine get-Methode mit der Semantik der get-Methode von List, was ein anderer Anwendungsfall ist als die Überprüfung, ob S. (A) enthält (was es wäre). Dies ist nicht einmal ein seltener Anwendungsfall für Sammlungen.
Tom Tresansky
0

Ja, verwenden Sie HashMap... aber auf spezielle Weise: Die Falle, die ich beim Versuch sehe, a HashMapals Pseudo- zu verwenden, Setist die mögliche Verwechslung zwischen "tatsächlichen" Elementen der Map/Setund "Kandidaten" -Elementen, dh Elementen, die zum Testen verwendet werden, ob ein equalElement ist bereits vorhanden. Dies ist alles andere als narrensicher, stößt Sie jedoch von der Falle weg:

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

Dann mach das:

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

Aber ... Sie möchten jetzt candidate, dass sich das Programm auf irgendeine Weise selbst zerstört, es sei denn, der Programmierer legt es tatsächlich sofort in das Map/Set... Sie möchten containsdas "beschmutzen", candidatedamit jede Verwendung, es sei denn, es verbindet sich mit dem Map"Anathema" ". Vielleicht könnten Sie SomeClasseine neue TaintableSchnittstelle implementieren lassen.

Eine zufriedenstellendere Lösung ist ein GettableSet ( siehe unten). Damit dies funktioniert, müssen Sie entweder für das Design von verantwortlich SomeClasssein, damit alle Konstruktoren nicht sichtbar sind (oder ... in der Lage und bereit sind, eine Wrapper-Klasse dafür zu entwerfen und zu verwenden):

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
}

Implementierung:

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

Ihre NoVisibleConstructorKlassen sehen dann so aus:

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 ein technisches Problem mit einer solchen NoVisibleConstructorKlasse: Es kann beanstandet werden, dass eine solche Klasse inhärent ist final, was unerwünscht sein kann. Eigentlich könnte man immer einen Dummy- protectedKonstruktor ohne Parameter hinzufügen :

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

... was zumindest eine Unterklasse kompilieren lassen würde. Sie müssten dann darüber nachdenken, ob Sie eine andere getOrCreate()Factory-Methode in die Unterklasse aufnehmen müssen.

Der letzte Schritt ist eine abstrakte Basisklasse (NB "Element" für eine Liste, "Mitglied" für eine Menge) wie diese für Ihre Gruppenmitglieder (wenn möglich - wieder Spielraum für die Verwendung einer Wrapper-Klasse, bei der die Klasse nicht unter Ihrer Kontrolle steht). oder hat bereits eine Basisklasse usw.) für maximales Ausblenden der Implementierung:

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

... Nutzung ist ziemlich offensichtlich (in Ihrer SomeClass‚s staticFactory - Methode):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
Mike Nagetier
quelle
0

Der Vertrag des Hash-Codes macht Folgendes klar:

"Wenn zwei Objekte gemäß der Object-Methode gleich sind, muss der Aufruf der hashCode-Methode für jedes der beiden Objekte das gleiche ganzzahlige Ergebnis liefern."

Also deine Annahme:

"Zur Verdeutlichung wird die Methode equals überschrieben, überprüft jedoch nur eines der Felder, nicht alle. Zwei Foo-Objekte, die als gleich angesehen werden, können also unterschiedliche Werte haben. Deshalb kann ich nicht einfach foo verwenden."

ist falsch und Sie brechen den Vertrag. Wenn wir uns die "enthält" -Methode der Set-Schnittstelle ansehen, haben wir Folgendes:

Boolescher Wert enthält (Objekt o);
Gibt true zurück, wenn diese Menge das angegebene Element enthält. Gibt formeller wahr zurück, wenn und nur wenn diese Menge ein Element "e" enthält, so dass o == null? e == null: o.equals (e)

Um das zu erreichen, was Sie wollen, können Sie eine Karte verwenden, in der Sie den Schlüssel definieren und Ihr Element mit dem Schlüssel speichern, der definiert, wie Objekte unterschiedlich oder gleich sind.

jfajunior
quelle
-2

Schnelle Hilfsmethode, die diese Situation beheben könnte:

<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
quelle
8
Es ist sehr seltsam, dass diese Antwort so viele positive Stimmen erhalten hat, da sie die Frage nicht beantwortet oder sogar versucht, sie in irgendeiner Weise anzusprechen.
David Conrad
-2

Das Folgende kann ein Ansatz sein

   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
quelle
-2

Versuchen Sie es mit einem Array:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
Canni
quelle