Wie kann ich in Java 8 eine Sammlung mithilfe der Stream
API filtern, indem ich die Unterscheidbarkeit einer Eigenschaft jedes Objekts überprüfe?
Zum Beispiel habe ich eine Liste von Person
Objekten und möchte Personen mit demselben Namen entfernen.
persons.stream().distinct();
Verwendet die Standard-Gleichheitsprüfung für ein Person
Objekt, also brauche ich so etwas wie:
persons.stream().distinct(p -> p.getName());
Leider hat die distinct()
Methode keine solche Überlastung. Ist Person
es möglich, dies kurz und bündig zu tun, ohne die Gleichheitsprüfung innerhalb der Klasse zu ändern?
quelle
Function<? super T, ?>
nicht seinFunction<? super T, Object>
. Es sollte auch beachtet werden, dass diese Lösung für geordnete parallele Streams nicht garantiert, welches Objekt extrahiert wird (im Gegensatz zu normaldistinct()
). Auch für sequentielle Streams entsteht ein zusätzlicher Aufwand für die Verwendung von CHM (der in der @ nosid-Lösung nicht vorhanden ist). Schließlich verstößt diese Lösung gegen denfilter
Methodenvertrag, dessen Prädikat zustandslos sein muss, wie in JavaDoc angegeben. Trotzdem positiv bewertet.distinctByKey
hat keine Ahnung, ob sie in einem parallelen Stream verwendet wird. Es verwendet CHM für den Fall, dass es parallel verwendet wird, obwohl dies im sequentiellen Fall, wie Tagir Valeev oben erwähnt, zusätzlichen Aufwand verursacht.distinctByKey
. Es funktioniert jedoch, wenn SiedistinctByKey
jedes Mal aufrufen , sodass jedes Mal eine neue Prädikatinstanz erstellt wird..filter(distinctByKey(...))
. Die Methode wird einmal ausgeführt und das Prädikat zurückgegeben. Grundsätzlich wird die Karte bereits wiederverwendet, wenn Sie sie in einem Stream ordnungsgemäß verwenden. Wenn Sie die Karte statisch machen würden, würde die Karte für alle Verwendungen freigegeben. Wenn Sie also zwei Streams verwendendistinctByKey()
, verwenden beide dieselbe Map, was nicht das ist, was Sie wollen.CallSite
wird mit derget$Lambda
Methode verknüpft. Dadurch wird immer eine neue Instanz zurückgegebenPredicate
, aber diese Instanzen haben nach meinem Verständnis dieselbemap
und dieselbefunction
. Sehr schön!Eine Alternative wäre, die Personen mit dem Namen als Schlüssel auf einer Karte zu platzieren:
Beachten Sie, dass die Person, die im Falle eines doppelten Namens aufbewahrt wird, die erste ist, die in Kontakt kommt.
quelle
distinct()
ohne diesen Aufwand zu implementieren ? Wie würde eine Implementierung wissen, ob sie ein Objekt zuvor gesehen hat, ohne sich tatsächlich an alle unterschiedlichen Werte zu erinnern, die sie gesehen hat? Der Overhead vontoMap
unddistinct
ist also sehr wahrscheinlich der gleiche.distinct()
selbst entsteht.persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
TreeSet
), die ohnehin schon verschieden ist odersorted
im Stream, die auch alle Elemente puffert.Sie können die Personenobjekte in eine andere Klasse einbinden, die nur die Namen der Personen vergleicht. Anschließend packen Sie die verpackten Objekte aus, um einen Personen-Stream erneut zu erhalten. Die Stream-Operationen könnten wie folgt aussehen:
Die Klasse
Wrapper
könnte wie folgt aussehen:quelle
equals
Methode kann vereinfacht werdenreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Eine andere Lösung mit
Set
. Vielleicht nicht die ideale Lösung, aber es funktioniertWenn Sie die ursprüngliche Liste ändern können, können Sie die Methode removeIf verwenden
quelle
Es gibt einen einfacheren Ansatz, ein TreeSet mit einem benutzerdefinierten Komparator zu verwenden.
quelle
Wir können auch RxJava (sehr leistungsfähige reaktive Erweiterungsbibliothek ) verwenden.
oder
quelle
Observable
ist Push-basiert, währendStream
Pull-basiert ist. stackoverflow.com/questions/30216979/…Flux.fromIterable(persons).distinct(p -> p.getName())
Stream
API", nicht "Nicht unbedingt Verwenden des Streams". Dies ist jedoch eine großartige Lösung für das XY-Problem, den Stream nach bestimmten Werten zu filtern.Sie können
groupingBy
Collector verwenden:Wenn Sie einen anderen Stream haben möchten, können Sie diesen verwenden:
quelle
Sie können die
distinct(HashingStrategy)
Methode in Eclipse-Sammlungen verwenden .Wenn Sie die
persons
Implementierung einer Eclipse Collections-Schnittstelle umgestalten können, können Sie die Methode direkt in der Liste aufrufen.HashingStrategy ist einfach eine Strategie-Schnittstelle, mit der Sie benutzerdefinierte Implementierungen von Equals und Hashcode definieren können.
Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
quelle
Ich empfehle die Verwendung von Vavr , wenn Sie können. Mit dieser Bibliothek können Sie Folgendes tun:
quelle
Sie können die StreamEx- Bibliothek verwenden:
quelle
String
dank String-Internierung für s funktionieren , aber möglicherweise auch nicht.Wenn Sie die Antwort von Stuart Marks erweitern, können Sie dies auf kürzere Weise und ohne gleichzeitige Zuordnung tun (wenn Sie keine parallelen Streams benötigen):
Dann ruf an:
quelle
Collections.synchronizedSet(new HashSet<>())
stattdessen eine erstellen würden . Aber es wäre wahrscheinlich langsamer als mit einemConcurrentHashMap
.Ähnlicher Ansatz, den Saeed Zarinfam verwendet hat, aber mehr Java 8-Stil :)
quelle
flatMap(plans -> plans.stream().findFirst().stream())
diese ersetzen und die Verwendung von get on Optional vermeidenIch habe eine generische Version gemacht:
Ein Beispiel:
quelle
Eine andere Bibliothek, die dies unterstützt, ist jOOλ und seine
Seq.distinct(Function<T,U>)
Methode:Unter der Haube macht es jedoch praktisch das Gleiche wie die akzeptierte Antwort .
quelle
quelle
Mein Ansatz dabei ist, alle Objekte mit derselben Eigenschaft zu gruppieren, dann die Gruppen auf die Größe 1 zu kürzen und sie schließlich als zu sammeln
List
.quelle
Eine Liste eindeutiger Objekte finden Sie unter:
quelle
Der einfachste Weg, dies zu implementieren, besteht darin, auf die Sortierfunktion zu springen, da sie bereits eine Option bereitstellt
Comparator
, die mithilfe der Eigenschaft eines Elements erstellt werden kann. Dann müssen Sie Duplikate herausfiltern, was mit einem statefull möglich ist,Predicate
der die Tatsache nutzt, dass für einen sortierten Stream alle gleichen Elemente benachbart sind:Natürlich ist ein Statefull
Predicate
nicht threadsicher, aber wenn Sie dies benötigen, können Sie diese Logik in eine verschiebenCollector
und den Stream für die Thread-Sicherheit sorgen lassen, wenn Sie Ihre verwendenCollector
. Dies hängt davon ab, was Sie mit dem Strom unterschiedlicher Elemente tun möchten, die Sie uns in Ihrer Frage nicht mitgeteilt haben.quelle
Aufbauend auf der Antwort von @ josketres habe ich eine generische Dienstprogrammmethode erstellt:
Sie können dies Java 8-freundlicher machen, indem Sie einen Collector erstellen .
quelle
Vielleicht wird es für jemanden nützlich sein. Ich hatte ein bisschen eine andere Anforderung. Wenn Sie eine Liste von Objekten
A
von Drittanbietern haben, entfernen Sie alle, die dasselbeA.b
Feld für dasselbe habenA.id
(mehrereA
Objekte mit demselbenA.id
in der Liste). Die Antwort auf die Stream-Partition von Tagir Valeev hat mich dazu inspiriert, einen benutzerdefinierten Code zu verwenden,Collector
der zurückgibtMap<A.id, List<A>>
. EinfachflatMap
wird den Rest erledigen.quelle
Ich hatte eine Situation, in der ich verschiedene Elemente aus der Liste basierend auf 2 Schlüsseln erhalten sollte. Versuchen Sie dies, wenn Sie anhand von zwei Schlüsseln oder einem zusammengesetzten Schlüssel unterscheiden möchten
quelle
In meinem Fall musste ich kontrollieren, was das vorherige Element war. Ich habe dann ein statusbehaftetes Prädikat erstellt, in dem ich gesteuert habe, ob sich das vorherige Element vom aktuellen Element unterscheidet. In diesem Fall habe ich es beibehalten.
quelle
Meine Lösung in dieser Auflistung:
In meiner Situation möchte ich unterschiedliche Werte finden und in die Liste aufnehmen.
quelle
Während die am höchsten bewertete Antwort die absolut beste Antwort für Java 8 ist, ist sie gleichzeitig absolut schlecht in Bezug auf die Leistung. Wenn Sie wirklich eine schlechte Anwendung mit geringer Leistung wünschen, verwenden Sie sie. Das einfache Erfordernis, einen eindeutigen Satz von Personennamen zu extrahieren, wird durch bloßes "Für jeden" und einen "Satz" erreicht. Es wird noch schlimmer, wenn die Liste über 10 liegt.
Angenommen, Sie haben eine Sammlung von 20 Objekten wie folgt:
Wo Ihr Objekt so
SimpleEvent
aussieht:Und zum Testen haben Sie einen JMH- Code wie diesen (Bitte beachten Sie, dass ich dasselbe eindeutige ByKey-Prädikat verwende, das in der akzeptierten Antwort erwähnt wird):
Dann haben Sie folgende Benchmark- Ergebnisse:
Und wie Sie sehen können, ist ein einfacher For-Each dreimal besser im Durchsatz und weniger fehlerhaft als Java 8 Stream.
Je höher der Durchsatz, desto besser die Leistung
quelle
quelle
Wenn Sie eine Liste der Personen erstellen möchten, ist dies der einfache Weg
Wenn Sie eine eindeutige oder eindeutige Liste von Namen und nicht von Personen suchen möchten , können Sie außerdem die folgenden zwei Methoden verwenden.
Methode 1: Verwenden
distinct
Methode 2: Verwenden
HashSet
quelle
Person
s.Der einfachste Code, den Sie schreiben können:
quelle