Jak w Javie 8 mogę filtrować kolekcję za pomocą Stream
interfejsu API, sprawdzając odrębność właściwości każdego obiektu?
Na przykład mam listę Person
obiektów i chcę usunąć osoby o tej samej nazwie,
persons.stream().distinct();
Użyje domyślnego sprawdzenia równości dla Person
obiektu, więc potrzebuję czegoś takiego,
persons.stream().distinct(p -> p.getName());
Niestety distinct()
metoda nie ma takiego przeciążenia. Czy bez modyfikacji kontroli równości w Person
klasie można to zrobić zwięźle?
źródło
Function<? super T, ?>
, nieFunction<? super T, Object>
. Należy również zauważyć, że dla uporządkowanego strumienia równoległego to rozwiązanie nie gwarantuje, który obiekt zostanie wyodrębniony (w przeciwieństwie do normalnegodistinct()
). Również w przypadku strumieni sekwencyjnych istnieje dodatkowy narzut związany z użyciem CHM (nieobecny w rozwiązaniu @nosid). Wreszcie, to rozwiązanie narusza umowęfilter
metody, której predykat musi być bezstanowy, jak podano w JavaDoc. Niemniej jednak głosowano.distinctByKey
nie ma pojęcia, czy jest używana w strumieniu równoległym. Używa CHM w przypadku, gdy jest używany równolegle, ale dodaje to narzut w przypadku sekwencyjnym, jak zauważył powyżej Tagir Valeev.distinctByKey
. Ale działa, jeśli wywołujesz zadistinctByKey
każdym razem, dzięki czemu za każdym razem tworzy nową instancję Predicate..filter(distinctByKey(...))
. Wykona metodę raz i zwróci predykat. Zasadniczo mapa jest już ponownie używana, jeśli używasz jej prawidłowo w strumieniu. Jeśli uczynisz mapę statyczną, mapa zostanie udostępniona dla wszystkich zastosowań. Więc jeśli masz dwa strumienie korzystające z tegodistinctByKey()
, oba użyłyby tej samej mapy, co nie jest tym, czego chcesz.CallSite
będzie powiązana zget$Lambda
metodą - która zwróci cały czas nową instancjęPredicate
, ale te instancje będą dzielić to samomap
ifunction
o ile rozumiem. Bardzo dobrze!Alternatywą byłoby umieszczenie osób na mapie przy użyciu nazwiska jako klucza:
Zauważ, że Osoba, która jest przechowywana, w przypadku duplikatu imienia, zostanie pierwsza wcielona w postać.
źródło
distinct()
bez tego narzutu? Skąd jakaś implementacja wiedziałaby, gdyby widział obiekt wcześniej, nie pamiętając wszystkich wyraźnych wartości, które widział? NarzuttoMap
idistinct
jest bardzo prawdopodobne, że jest taki sam.distinct()
sama w sobie tworzy.persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
TreeSet
), które i tak jest już odrębne lubsorted
w strumieniu, który buforuje również wszystkie elementy.Możesz zawinąć obiekty osoby w inną klasę, która porównuje tylko nazwiska osób. Następnie rozpakowujesz zawinięte obiekty, aby ponownie uzyskać strumień osoby. Operacje strumieniowe mogą wyglądać następująco:
Klasa
Wrapper
może wyglądać następująco:źródło
equals
Sposób można uprościćreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Inne rozwiązanie, przy użyciu
Set
. Może nie być idealnym rozwiązaniem, ale działaLub jeśli możesz zmodyfikować oryginalną listę, możesz użyć metody removeIf
źródło
Jest prostsze podejście przy użyciu TreeSet z niestandardowym komparatorem.
źródło
Możemy również użyć RxJava (bardzo rozbudowana biblioteka rozszerzeń reaktywnych )
lub
źródło
Observable
jest oparty na push, podczas gdyStream
jest oparty na pull. stackoverflow.com/questions/30216979/…Flux.fromIterable(persons).distinct(p -> p.getName())
Stream
interfejsu API”, a nie „niekoniecznie za pomocą strumienia”. To powiedziawszy, jest to świetne rozwiązanie problemu XY filtrowania strumienia do różnych wartości.Możesz użyć
groupingBy
kolektora:Jeśli chcesz mieć inny strumień, możesz użyć tego:
źródło
Możesz użyć tej
distinct(HashingStrategy)
metody w kolekcji Eclipse .Jeśli możesz refaktoryzować w
persons
celu wdrożenia interfejsu kolekcji Eclipse, możesz wywołać tę metodę bezpośrednio na liście.HashingStrategy to po prostu interfejs strategii, który pozwala definiować niestandardowe implementacje równości i kodu mieszającego.
Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.
źródło
Polecam korzystanie z Vavr , jeśli możesz. Za pomocą tej biblioteki możesz wykonać następujące czynności:
źródło
Możesz użyć biblioteki StreamEx :
źródło
String
dzięki internowaniu łańcuchów, ale może też nie.Rozszerzając odpowiedź Stuarta Marksa, można to zrobić w krótszy sposób i bez jednoczesnej mapy (jeśli nie potrzebujesz równoległych strumieni):
Wtedy zadzwoń:
źródło
Collections.synchronizedSet(new HashSet<>())
zamiast niego. Ale prawdopodobnie byłoby wolniej niż zConcurrentHashMap
.Podobne podejście zastosował Saeed Zarinfam, ale bardziej styl Java 8 :)
źródło
flatMap(plans -> plans.stream().findFirst().stream())
aby uniknąć korzystania z opcji OpcjonalneZrobiłem ogólną wersję:
Przykład:
źródło
źródło
Inną biblioteką, która to obsługuje, jest jOOλ i jej
Seq.distinct(Function<T,U>)
metoda:Pod maską robi to praktycznie to samo, co przyjęta odpowiedź .
źródło
Moje podejście do tego polega na zgrupowaniu wszystkich obiektów o tej samej właściwości, a następnie skróceniu grup do rozmiaru 1, a następnie zebraniu ich jako
List
.źródło
Odrębną listę obiektów można znaleźć za pomocą:
źródło
Najłatwiejszym sposobem na wdrożenie tego jest przeskoczenie na funkcję sortowania, ponieważ już zapewnia ona opcję opcjonalną,
Comparator
którą można utworzyć za pomocą właściwości elementu. Następnie musisz odfiltrować duplikaty, co można zrobić za pomocą statefull,Predicate
który wykorzystuje fakt, że dla posortowanego strumienia wszystkie równe elementy sąsiadują:Oczywiście stanowe
Predicate
nie jest bezpieczne dla wątków, jednak jeśli tego potrzebujesz, możesz przenieść tę logikę doCollector
i pozwolić, aby strumień zadbał o bezpieczeństwo wątków podczas używania twojegoCollector
. Zależy to od tego, co chcesz zrobić ze strumieniem różnych elementów, których nie powiedziałeś nam w swoim pytaniu.źródło
Opierając się na odpowiedzi @ josketres, stworzyłem ogólną metodę użyteczności:
Możesz uczynić to bardziej przyjaznym dla Java 8, tworząc Collector .
źródło
Może przyda się komuś. Miałem trochę inny wymóg. Posiadanie listy obiektów
A
innych firm usuwa wszystkie, które mają to samoA.b
pole dla tego samegoA.id
(wieleA
obiektów z tym samymA.id
na liście). Tagir Valeev, autor odpowiedzi na partycję strumieniową, zainspirował mnie do użycia niestandardowego, który zwraca . Simple zrobi resztę.Collector
Map<A.id, List<A>>
flatMap
źródło
Miałem sytuację, w której miałem uzyskać różne elementy z listy opartej na 2 kluczach. Jeśli chcesz odróżniać na podstawie dwóch kluczy lub może klucza złożonego, spróbuj tego
źródło
W moim przypadku musiałem kontrolować, co było poprzednim elementem. Potem stworzył pełnostanowego predykatu gdzie kontrolowane jeśli poprzedni elementem różnił się od bieżącego elementu, w tym przypadku trzymałem go.
źródło
Moje rozwiązanie na tej liście:
W mojej sytuacji chcę znaleźć odrębne wartości i umieścić je na liście.
źródło
Chociaż najwyższa pozytywna odpowiedź jest absolutnie najlepszą odpowiedzią w Javie 8, jest jednocześnie absolutnie najgorsza pod względem wydajności. Jeśli naprawdę chcesz mieć kiepską aplikację o niskiej wydajności, skorzystaj z niej. Prosty wymóg wyodrębnienia unikalnego zestawu Nazwisk Osób zostanie osiągnięty przez zwykłe „For-Each” i „Set”. Gorzej, jeśli lista jest powyżej 10.
Rozważ, że masz kolekcję 20 obiektów, takich jak to:
Twój obiekt
SimpleEvent
wygląda następująco:Aby przetestować, masz taki kod JMH (Uwaga: używam tego samego odrębnego predykatuByKey wymienionego w zaakceptowanej odpowiedzi):
Następnie uzyskasz wyniki testu porównawczego :
I jak widać, prosty For-Each ma 3 razy lepszą przepustowość i mniejszy wynik błędu w porównaniu do Java 8 Stream.
Wyższa przepustowość, lepsza wydajność
źródło
źródło
Jeśli chcesz wyświetlić listę osób, prosty sposób będzie następujący
Dodatkowo, jeśli chcesz znaleźć odrębną lub unikalną listę nazwisk , nie Osoba , możesz to zrobić, stosując dwie następujące metody.
Metoda 1: użycie
distinct
Metoda 2: użycie
HashSet
źródło
Person
a nie s.Najprostszy kod, jaki możesz napisać:
źródło