Nowa struktura strumieniowa Java 8 i znajomi tworzą bardzo zwięzły kod java, ale natknąłem się na pozornie prostą sytuację, której wykonanie jest trudne.
Zastanów się nad List<Thing> things
metodą i Optional<Other> resolve(Thing thing)
. Chcę zmapować Thing
s do Optional<Other>
s i uzyskać pierwszy Other
. Oczywistym rozwiązaniem byłoby użycie things.stream().flatMap(this::resolve).findFirst()
, ale flatMap
wymaga zwrotu strumienia i Optional
nie ma stream()
metody (lub jest to Collection
metoda lub udostępnia metodę konwersji na nią lub wyświetlenia jako a Collection
).
Najlepsze, co mogę wymyślić, to:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Ale to wydaje się okropnie długo rozwiązywane, co wydaje się bardzo częstym przypadkiem. Czy ktoś ma lepszy pomysł?
java
lambda
java-8
java-stream
Yona Appletree
źródło
źródło
.flatMap(Optional::toStream)
, w twojej wersji faktycznie widzisz, co się dzieje.Optional.stream
istnieje już w JDK 9 ....Odpowiedzi:
Java 9
Optional.stream
został dodany do JDK 9. Umożliwia to wykonywanie następujących czynności, bez potrzeby stosowania żadnej metody pomocniczej:Java 8
Tak, to była niewielka dziura w interfejsie API, ponieważ nieco niewygodne jest przekształcenie
Optional<T>
w długość zero lub jedenStream<T>
. Możesz to zrobić:Posiadanie operatora trójskładnikowego w środku
flatMap
jest jednak nieco kłopotliwe, więc może być lepiej napisać małą funkcję pomocniczą, aby to zrobić:Tutaj podałem wezwanie do
resolve()
zamiast osobnejmap()
operacji, ale to kwestia gustu.źródło
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
przeciążenia doStream#flatMap
... w ten sposób można po prostu pisaćstream().flatMap(this::resolve)
Optional.stream()
.Dodaję drugą odpowiedź na podstawie proponowanej edycji przez użytkownika srborlongan do mojej drugiej odpowiedzi . Myślę, że zaproponowana technika była interesująca, ale nie nadawała się do edycji mojej odpowiedzi. Inni zgodzili się, a proponowana zmiana została odrzucona. (Nie byłem jednym z głosujących.) Ta technika ma jednak swoje zalety. Byłoby najlepiej, gdyby srborlongan opublikował własną odpowiedź. To się jeszcze nie zdarzyło i nie chciałem, aby technika zaginęła we mgle StackOverflow, odrzuciła historię edycji, więc postanowiłem przedstawić ją jako osobną odpowiedź.
Zasadniczo technika polega na użyciu niektórych
Optional
metod w sprytny sposób, aby uniknąć konieczności używania operatora trójskładnikowego (? :
) lub instrukcji if / else.Mój wbudowany przykład zostałby przepisany w ten sposób:
Mój przykład wykorzystujący metodę pomocniczą zostałby przepisany w następujący sposób:
KOMENTARZ
Porównajmy bezpośrednio oryginalne i zmodyfikowane wersje:
Oryginał jest prosty, jeśli roboczy podejście: otrzymujemy
Optional<Other>
; jeśli ma wartość, zwracamy strumień zawierający tę wartość, a jeśli nie ma żadnej wartości, zwracamy pusty strumień. Całkiem proste i łatwe do wyjaśnienia.Modyfikacja jest sprytna i ma tę zaletę, że pozwala uniknąć warunków warunkowych. (Wiem, że niektórzy ludzie nie lubią trójskładnikowego operatora. W przypadku niewłaściwego użycia, kod może być trudny do zrozumienia). Czasami jednak rzeczy mogą być zbyt sprytne. Zmodyfikowany kod zaczyna się również od
Optional<Other>
. Następnie wywołuje,Optional.map
co jest zdefiniowane następująco:map(Stream::of)
Wywołanie zwracaOptional<Stream<Other>>
. Jeśli wartość wejściowa była obecna Opcjonalnie, zwrócona Opcjonalnie zawiera strumień, który zawiera pojedynczy wynik Inne. Ale jeśli wartość nie była obecna, wynikiem jest pusta opcja.Następnie wywołanie
orElseGet(Stream::empty)
zwraca wartość typuStream<Other>
. Jeśli jego wartość wejściowa jest obecna, otrzymuje wartość, która jest pojedynczym elementemStream<Other>
. W przeciwnym razie (jeśli wartość wejściowa jest nieobecna) zwraca wartość pustąStream<Other>
. Tak więc wynik jest poprawny, taki sam jak oryginalny kod warunkowy.W komentarzach do mojej odpowiedzi dotyczącej odrzuconej edycji opisałem tę technikę jako „bardziej zwięzłą, ale także bardziej niejasną”. Stoję przy tym. Zajęło mi trochę czasu, aby dowiedzieć się, co robi, a także zajęło mi trochę czasu, aby napisać powyższy opis tego, co robi. Kluczową subtelnością jest transformacja z
Optional<Other>
naOptional<Stream<Other>>
. Kiedy już to zrozumiesz, ma to sens, ale nie było dla mnie oczywiste.Przyznaję jednak, że rzeczy, które początkowo są niejasne, z czasem mogą stać się idiomatyczne. Może się zdarzyć, że ta technika stanie się najlepszym sposobem w praktyce, przynajmniej do czasu
Optional.stream
dodania (jeśli w ogóle).AKTUALIZACJA:
Optional.stream
dodano do JDK 9.źródło
Nie możesz zrobić tego bardziej zwięźle, jak już to robisz.
Twierdzisz, że nie chcesz
.filter(Optional::isPresent)
i.map(Optional::get)
.Zostało to rozwiązane za pomocą metody @StuartMarks, jednak w rezultacie mapujesz go teraz na
Optional<T>
, więc teraz musisz go użyć.flatMap(this::streamopt)
iget()
na końcu.Więc nadal składa się z dwóch instrukcji i teraz możesz uzyskać wyjątki dzięki nowej metodzie! Bo co jeśli każda opcja jest pusta? Wtedy
findFirst()
zwróci pustą opcjonalną i twojaget()
porażka!Co masz:
jest w rzeczywistości najlepszym sposobem na osiągnięcie tego, czego chcesz, i chcesz zapisać wynik jako, a
T
nie jakoOptional<T>
.Pozwoliłem sobie tworzenia
CustomOptional<T>
klasy, która otulaOptional<T>
i zapewnia dodatkową metodęflatStream()
. Pamiętaj, że nie możesz przedłużyćOptional<T>
:Zobaczysz, że dodałem
flatStream()
, jak tutaj:Użyty jako:
Państwo nadal będą musiały zwracają
Stream<T>
tutaj, ponieważ nie może wrócićT
, bo jeśli!optional.isPresent()
, toT == null
jeśli uznaniu jej za takie, ale wówczas.flatMap(CustomOptional::flatStream)
będzie próbował dodaćnull
do strumienia i że nie jest to możliwe.Jako przykład:
Użyty jako:
Teraz rzuci
NullPointerException
operację wewnątrz strumienia.Wniosek
Zastosowana metoda jest w rzeczywistości najlepszą metodą.
źródło
Nieco krótsza wersja wykorzystująca
reduce
:Możesz również przenieść funkcję zmniejszania do statycznej metody użyteczności, a wtedy stanie się ona:
źródło
Ponieważ moja poprzednia odpowiedź nie wydawała się zbyt popularna, spróbuję jeszcze raz.
Krótka odpowiedź:
Przeważnie jesteś na dobrej drodze. Najkrótszy kod, aby uzyskać pożądany wynik, jaki mogę wymyślić, to:
To spełni wszystkie Twoje wymagania:
Optional<Result>
this::resolve
się leniwie, ile potrzebathis::resolve
nie zostanie wywołany po pierwszym niepustym wynikuOptional<Result>
Dłuższa odpowiedź
Jedyną modyfikacją w porównaniu do początkowej wersji OP było to, że usunąłem
.map(Optional::get)
przed połączeniem.findFirst()
i dodałem.flatMap(o -> o)
jako ostatnie połączenie w łańcuchu.Daje to przyjemny efekt pozbycia się podwójnej Opcji, ilekroć strumień znajdzie rzeczywisty wynik.
Tak naprawdę nie można iść krócej niż to w Javie.
Alternatywny fragment kodu wykorzystujący bardziej konwencjonalną
for
technikę pętli będzie miał mniej więcej tę samą liczbę wierszy kodu i będzie miał mniej więcej taką samą kolejność i liczbę operacji, które należy wykonać:this.resolve
,Optional.isPresent
Aby udowodnić, że moje rozwiązanie działa zgodnie z reklamą, napisałem mały program testowy:
(Ma kilka dodatkowych linii do debugowania i weryfikacji, że tylko tyle wywołań do rozwiązania, ile potrzeba ...)
Wykonując to w wierszu polecenia, otrzymałem następujące wyniki:
źródło
Jeśli nie masz nic przeciwko korzystaniu z biblioteki innej firmy, możesz użyć JavaScript . Jest jak Scala, ale zaimplementowany w Javie.
Jest wyposażony w kompletną niezmienną bibliotekę kolekcji, która jest bardzo podobna do znanej ze Scali. Te kolekcje zastępują kolekcje Javy i Java 8's Stream. Ma także własną implementację Opcji.
Oto rozwiązanie dla przykładu pytania początkowego:
Oświadczenie: Jestem twórcą języka JavaScript.
źródło
Późno na imprezę, ale co z tym
Możesz pozbyć się ostatniej metody get (), jeśli utworzysz metodę util do konwersji opcjonalnej do strumieniowego przesyłania strumieniowego:
Jeśli zwrócisz strumień od razu z funkcji rozstrzygania, zapisujesz jeszcze jedną linię.
źródło
Chciałbym promować fabryczne metody tworzenia pomocników dla funkcjonalnych interfejsów API:
Metoda fabryczna:
Rozumowanie:
Podobnie jak w przypadku odniesień do metod ogólnie, w porównaniu do wyrażeń lambda, nie można przypadkowo przechwycić zmiennej z dostępnego zakresu, na przykład:
t -> streamopt(resolve(o))
Składa się, możesz np. Wywołać
Function::andThen
wynik metody fabrycznej:streamopt(this::resolve).andThen(...)
Podczas gdy w przypadku lambda musisz ją najpierw rzucić:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
źródło
Null jest obsługiwany przez strumień dostarczony Moja biblioteka AbacusUtil . Oto kod:
źródło
Jeśli utknąłeś w Javie 8, ale masz dostęp do Guava 21.0 lub nowszej wersji, możesz użyć
Streams.stream
do konwersji opcjonalnej w strumień.Tak więc dane
Możesz pisać
źródło
Co z tym?
https://stackoverflow.com/a/58281000/3477539
źródło
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, podobnie jak pytanie (i linkowana odpowiedź) ma ....get()
bezisPresent()
, otrzymasz ostrzeżenie w IntelliJNajprawdopodobniej robisz to źle.
Java 8 Opcjonalna nie jest przeznaczona do użycia w ten sposób. Zazwyczaj jest zarezerwowane tylko dla operacji strumienia terminali, które mogą, ale nie muszą zwracać wartości, jak na przykład find.
W twoim przypadku może być lepiej najpierw spróbować znaleźć tani sposób, aby odfiltrować te elementy, które można rozwiązać, a następnie uzyskać pierwszy element jako opcjonalny i rozwiązać go jako ostatnią operację. Jeszcze lepiej - zamiast filtrować, znajdź pierwszy przedmiot do rozwiązania i rozwiąż go.
Ogólna zasada jest taka, że powinieneś starać się zmniejszyć liczbę elementów w strumieniu, zanim przekształcisz je w coś innego. Oczywiście YMMV.
źródło
Optional<Result> searchFor(Term t)
. To wydaje się pasować do intencji Opcjonalnej. Ponadto stream () powinien być leniwie oceniany, więc nie powinno się pojawiać żadnych dodatkowych zadań rozwiązujących warunki po pierwszym pasującym.