Próbuję użyć Java 8 Stream
do znalezienia elementów w LinkedList
. Chcę jednak zagwarantować, że istnieje jedno i tylko jedno dopasowanie do kryteriów filtru.
Weź ten kod:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
@Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
Ten kod znajduje się User
na podstawie ich identyfikatora. Ale nie ma gwarancji, ile User
s pasuje do filtra.
Zmiana linii filtra na:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Rzuci NoSuchElementException
(dobrze!)
Chciałbym jednak, aby generowało błąd, jeśli występuje wiele dopasowań. Czy jest na to sposób?
java
lambda
java-8
java-stream
ryvantage
źródło
źródło
count()
jest operacją terminalową, więc nie możesz tego zrobić. Nie można później użyć strumienia.Stream::size
?Stream
o wiele więcej niż wcześniej ...LinkedHashSet
(zakładając, że chcesz zachować porządek wstawiania) lubHashSet
cały czas. Jeśli twoja kolekcja służy tylko do znalezienia jednego identyfikatora użytkownika, to dlaczego zbierasz wszystkie pozostałe elementy? Jeśli istnieje potencjał, że zawsze będziesz musiał znaleźć identyfikator użytkownika, który również musi być unikalny, to po co używać listy, a nie zestawu? Programujesz wstecz. Użyj odpowiedniej kolekcji do pracy i oszczędzaj sobie bólu głowyOdpowiedzi:
Utwórz niestandardowy
Collector
Używamy
Collectors.collectingAndThen
do budowy naszych pożądanychCollector
przezList
zCollectors.toList()
kolektora.IllegalStateException
iflist.size != 1
.Użyty jako:
Następnie możesz dostosować to
Collector
tyle, ile chcesz, na przykład podać wyjątek jako argument w konstruktorze, dostosować go, aby pozwolić na dwie wartości i więcej.Alternatywne - prawdopodobnie mniej eleganckie - rozwiązanie:
Możesz użyć „obejścia” obejmującego
peek()
iAtomicInteger
, ale tak naprawdę nie powinieneś tego używać.To, co możesz zrobić, to po prostu zebranie go w następujący sposób
List
:źródło
Iterables.getOnlyElement
skróci te rozwiązania i zapewni lepsze komunikaty o błędach. Podobnie jak wskazówka dla czytelników, którzy już korzystają z Google Guava.singletonCollector()
definicji przestarzałej dla wersji, która pozostaje w poście, i zmiana jego nazwy natoSingleton()
. Moja wiedza na temat strumieni Java jest nieco zardzewiała, ale zmiana nazwy wydaje mi się pomocna. Przejrzenie tej zmiany zajęło mi 2 minuty, szczyty. Jeśli nie masz czasu na przeglądanie zmian, czy mogę zasugerować, aby poprosić kogoś innego o zrobienie tego w przyszłości, być może na czacie Java ?Dla kompletności, oto „linijka” odpowiadająca doskonałej odpowiedzi @ prunge:
Uzyskuje to jedyny pasujący element ze strumienia, rzucając
NoSuchElementException
w przypadku gdy strumień jest pusty, lubIllegalStateException
w przypadku gdy strumień zawiera więcej niż jeden pasujący element.Odmiana tego podejścia pozwala uniknąć wcześniejszego wyjątku i zamiast tego reprezentuje wynik jako
Optional
zawierający jedyny element lub nic (pusty), jeśli jest zero lub wiele elementów:źródło
get()
orElseThrow()
Inne odpowiedzi dotyczące pisania zwyczaju
Collector
są prawdopodobnie bardziej wydajne (takie jak odpowiedź Louisa Wassermana , +1), ale jeśli chcesz mieć zwięzłość, proponuję następujące:Następnie sprawdź rozmiar listy wyników.
źródło
limit(2)
tego rozwiązania? Jaką różnicę miałoby to, czy wynikowa lista to 2, czy 100? Jeśli jest większy niż 1.Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })
maxSize: the number of elements the stream should be limited to
. Więc nie powinno być.limit(1)
zamiast.limit(2)
?result.size()
czy jest równy 1. Jeśli jest to 2, oznacza to, że występuje więcej niż jedno dopasowanie, więc jest to błąd. Jeśli kod tak zrobiłlimit(1)
, więcej niż jedno dopasowanie spowodowałoby pojedynczy element, którego nie można odróżnić od dokładnie jednego dopasowania. Pominąłoby to przypadek błędu, który dotyczył PO.Guawa zapewnia
MoreCollectors.onlyElement()
, że robi to dobrze. Ale jeśli musisz to zrobić sam, możesz rzucić na to własneCollector
:... lub używając własnego
Holder
typu zamiastAtomicReference
. Możesz użyć tegoCollector
tyle, ile chcesz.źródło
Collector
był drogą.List
jest droższe niż pojedyncza zmienna odniesienia.MoreCollectors.onlyElement()
powinno faktycznie być pierwsze (i być może jedyne :))Użyj Guava's
MoreCollectors.onlyElement()
( JavaDoc ).Robi to, co chcesz i rzuca,
IllegalArgumentException
jeśli strumień składa się z dwóch lub więcej elementów, aNoSuchElementException
jeśli strumień jest pusty.Stosowanie:
źródło
MoreCollectors
jest częścią jeszcze nieopublikowanej (od 2016-12 r.) Niepublikowanej wersji 21.Operacja „włazu ewakuacyjnego”, która pozwala robić dziwne rzeczy, które nie są obsługiwane przez strumienie, polega na proszeniu o
Iterator
:Guawa ma wygodną metodę, aby wziąć
Iterator
i zdobyć jedyny element, rzucając, jeśli jest zero lub wiele elementów, które mogłyby zastąpić tutaj dolne linie n-1.źródło
Aktualizacja
Niezła sugestia w komentarzu @Holger:
Oryginalna odpowiedź
Zgłaszany jest wyjątek
Optional#get
, ale jeśli masz więcej niż jeden element, to nie pomoże. Możesz zebrać użytkowników w kolekcji, która akceptuje tylko jeden element, na przykład:co rzuca
java.lang.IllegalStateException: Queue full
, ale wydaje się zbyt hacking.Lub możesz użyć redukcji w połączeniu z opcjonalnym:
Zmniejszenie zasadniczo zwraca:
Wynik jest następnie pakowany w opcjonalny.
Ale najprostszym rozwiązaniem byłoby po prostu zebranie do kolekcji, sprawdzenie, czy jej rozmiar to 1 i uzyskanie jedynego elementu.
źródło
null
), aby zapobiec użyciuget()
. Niestety twójreduce
nie działa tak, jak myślisz, weź pod uwagę,Stream
że manull
w nim elementy, może uważasz, że to zakryłeś, ale ja mogę[User#1, null, User#2, null, User#3]
, teraz nie rzucę wyjątku, chyba że się tutaj mylę.null
do funkcji redukcji, usuwając wartość tożsamość argumentu uczyniłoby całą zajmujących sięnull
w funkcji przestarzałego:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
spełnia swoje zadanie, a nawet lepiej, to już zwracaOptional
, eliding konieczność za telefonOptional.ofNullable
na wynik.Alternatywą jest użycie redukcji: (w tym przykładzie użyto ciągów znaków, ale można je łatwo zastosować do dowolnego typu obiektu, w tym
User
)Więc w przypadku
User
ciebie miałbyś:źródło
Korzystanie z redukcji
To jest prostszy i elastyczny sposób, jaki znalazłem (na podstawie odpowiedzi @prunge)
W ten sposób otrzymujesz:
Optional.empty()
jeśli nie jest obecnyźródło
Myślę, że ten sposób jest prostszy:
źródło
Korzystanie z
Collector
:Stosowanie:
Zwracamy
Optional
, ponieważ zwykle nie możemy założyć, żeCollection
zawiera dokładnie jeden element. Jeśli już wiesz, że tak jest, zadzwoń:Obciąża to osobę wywołującą błąd - tak jak powinno.
źródło
Guava ma
Collector
do tego tzwMoreCollectors.onlyElement()
.źródło
Możemy użyć RxJava (bardzo rozbudowanej biblioteki rozszerzeń reaktywnych )
Pojedynczy operator zgłasza wyjątek, jeżeli nie użytkownika lub więcej niż jeden użytkownik znajduje.
źródło
Ponieważ
Collectors.toMap(keyMapper, valueMapper)
wykorzystuje rzutowanie fuzji do obsługi wielu wpisów za pomocą tego samego klucza, jest to łatwe:Dostaniesz
IllegalStateException
za duplikaty kluczy. Ale na koniec nie jestem pewien, czy kod nie byłby jeszcze bardziej czytelny przy użyciuif
.źródło
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, masz bardziej ogólne zachowanie.Używam tych dwóch kolektorów:
źródło
onlyOne()
zgłaszaIllegalStateException
dla> 1 elementów, a NoSuchElementException` (inOptional::get
) dla 0 elementów.Supplier
z(Runtime)Exception
.Jeśli nie masz nic przeciwko korzystaniu z biblioteki innej firmy,
SequenceM
z cyklop-strumieni (iLazyFutureStream
od prostej reakcji ) oba mają operatory pojedyncze i pojedynczeOpcjonalne.singleOptional()
zgłasza wyjątek, jeśli są w nim elementy0
lub więcej1
, wStream
przeciwnym razie zwraca pojedynczą wartość.singleOptional()
zwraca,Optional.empty()
jeśli nie ma wartości lub więcej niż jedną wartość wStream
.Ujawnienie - jestem autorem obu bibliotek.
źródło
Poszedłem z podejściem bezpośrednim i właśnie wdrożyłem rzecz:
z testem JUnit:
Ta implementacja nie jest wątkowo bezpieczna.
źródło
źródło
Próbowałeś tego?
Źródło: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
źródło
count()
nie jest dobry w użyciu, ponieważ jest to operacja terminalowa.