Warum wird keine Set
Operation 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 Set
ein Element enthält, das gleich bar
ist. Warum kann ich dieses Element nicht erhalten? :((
Zur Verdeutlichung wird die equals
Methode überschrieben, überprüft jedoch nur eines der Felder, nicht alle. Zwei Foo
Objekte, die als gleich angesehen werden, können tatsächlich unterschiedliche Werte haben. Deshalb kann ich sie nicht einfach verwenden foo
.
java
collections
set
equals
foobar
quelle
quelle
SortedSet
und dessen Implementierungen, die kartenbasiert sind (z. B.TreeSet
Zugriff ermöglichenfirst()
).NSSet
) hat eine solche Methode. Es wird aufgerufenmember
und gibt das Objekt innerhalb der Menge zurück, das "gleich" mit dem Parameter dermember
Methode vergleicht (was natürlich ein anderes Objekt sein kann und auch andere Eigenschaften hat, die gleich möglicherweise nicht prüfen).Antworten:
Es wäre sinnlos, das Element zu erhalten, wenn es gleich ist. A
Map
ist 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:
quelle
Map
besser geeignet ist (Map<Foo, Foo>
in diesem Fall)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.Um die genaue Frage zu beantworten: " Warum wird keine
Set
Operation 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ützlicheget()
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 verwendenSet<E>
, um die Elemente sich selbst zuzuordnen. Auf diese Weise können Sie ein Element effizient aus der "Menge" abrufen, da die get () -Methode vonMap
das Element mithilfe einer effizienten Hash-Tabelle oder eines Baum-Algorithmus findet. Wenn Sie möchten, können Sie Ihre eigene Implementierung schreibenSet
, die die zusätzlicheget()
Methode bietet, die dieMap
.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
Set
als 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 fehlendenget()
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.quelle
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).Wenn Sie ein gleiches Objekt haben, warum brauchen Sie das aus dem Set? Wenn es nur durch einen Schlüssel "gleich" ist,
Map
wäre ein eine bessere Wahl.Wie auch immer, das wird es tun:
Mit Java 8 kann dies ein Einzeiler werden:
quelle
Konvertieren Sie den Satz in eine Liste und verwenden Sie dann die
get
Listenmethodequelle
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.
quelle
Wenn Ihr Set tatsächlich ein
NavigableSet<Foo>
(wie einTreeSet
) ist, undFoo implements Comparable<Foo>
, können Sie verwenden(Danke an @ eliran-malkas Kommentar für den Hinweis.)
quelle
Mit Java 8 können Sie Folgendes tun:
Aber seien Sie vorsichtig, .get () löst eine NoSuchElementException aus, oder Sie können ein optionales Element bearbeiten.
quelle
item->item.equals(theItemYouAreLookingFor)
kann verkürzt werden auftheItemYouAreLookingFor::equals
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.
quelle
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
Wie:
Nachdem Streams eingeführt wurden, können Sie Folgendes tun
quelle
Was ist mit der Arrays-Klasse?
Ausgabe:
Punkte eins, zwei
quelle
Verwenden Sie dazu besser das Java HashMap-Objekt http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
quelle
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.
quelle
Kenne ich schon!! Wenn Sie Guava verwenden, können Sie es schnell in eine Karte konvertieren:
quelle
Sie können die Iterator-Klasse verwenden
quelle
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.
quelle
Wenn Sie sich die ersten Zeilen der Implementierung ansehen, werden
java.util.HashSet
Sie sehen:So
HashSet
VerwendungenHashMap
interally sowieso, was bedeutet , dass , wenn Sie nur verwenden ,HashMap
werden Sie den Effekt , den Sie und einige Speicher speichern selbst wollen Sie direkt und verwenden den gleichen Wert wie der Schlüssel und Wert.quelle
Es sieht so aus, als wäre das richtige Objekt der Interner von Guave:
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).
quelle
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 eineget(E element)
Methode in der Schnittstelle täuschen, da sie die Sammlung iterieren müsste, um das zurückzugebende Element zu finden, und aget(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.quelle
Ja, verwenden Sie
HashMap
... aber auf spezielle Weise: Die Falle, die ich beim Versuch sehe, aHashMap
als Pseudo- zu verwenden,Set
ist die mögliche Verwechslung zwischen "tatsächlichen" Elementen derMap/Set
und "Kandidaten" -Elementen, dh Elementen, die zum Testen verwendet werden, ob einequal
Element ist bereits vorhanden. Dies ist alles andere als narrensicher, stößt Sie jedoch von der Falle weg:Dann mach das:
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 dasMap/Set
... Sie möchtencontains
das "beschmutzen",candidate
damit jede Verwendung, es sei denn, es verbindet sich mit demMap
"Anathema" ". Vielleicht könnten SieSomeClass
eine neueTaintable
Schnittstelle implementieren lassen.Eine zufriedenstellendere Lösung ist ein GettableSet ( siehe unten). Damit dies funktioniert, müssen Sie entweder für das Design von verantwortlich
SomeClass
sein, damit alle Konstruktoren nicht sichtbar sind (oder ... in der Lage und bereit sind, eine Wrapper-Klasse dafür zu entwerfen und zu verwenden):Implementierung:
Ihre
NoVisibleConstructor
Klassen sehen dann so aus:PS ein technisches Problem mit einer solchen
NoVisibleConstructor
Klasse: Es kann beanstandet werden, dass eine solche Klasse inhärent istfinal
, was unerwünscht sein kann. Eigentlich könnte man immer einen Dummy-protected
Konstruktor ohne Parameter hinzufügen :... 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:
... Nutzung ist ziemlich offensichtlich (in Ihrer
SomeClass
‚sstatic
Factory - Methode):quelle
Der Vertrag des Hash-Codes macht Folgendes klar:
Also deine Annahme:
ist falsch und Sie brechen den Vertrag. Wenn wir uns die "enthält" -Methode der Set-Schnittstelle ansehen, haben wir Folgendes:
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.
quelle
Schnelle Hilfsmethode, die diese Situation beheben könnte:
quelle
Das Folgende kann ein Ansatz sein
quelle
Versuchen Sie es mit einem Array:
quelle