Mam listę, w myListToParse
której chcę filtrować elementy, zastosować metodę do każdego elementu i dodać wynik do innej listy myFinalList
.
W Javie 8 zauważyłem, że mogę to zrobić na 2 różne sposoby. Chciałbym poznać bardziej skuteczny sposób między nimi i zrozumieć, dlaczego jeden sposób jest lepszy od drugiego.
Jestem otwarty na wszelkie sugestie dotyczące trzeciego sposobu.
Metoda 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Metoda 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Emilien Brigand
źródło
źródło
elt -> elt != null
można go zastąpićObjects::nonNull
Optional<T>
zamiast tego używać w połączeniu zflatMap
..map(this::doSomething)
zakładając, żedoSomething
jest to metoda niestatyczna. Jeśli jest statyczny, możesz go zastąpićthis
nazwą klasy.Odpowiedzi:
Nie martw się o różnice w wydajności, zwykle będą minimalne w tym przypadku.
Preferowana jest metoda 2, ponieważ
nie wymaga mutowania kolekcji, która istnieje poza wyrażeniem lambda,
jest bardziej czytelny, ponieważ różne kroki wykonywane w potoku gromadzenia są zapisywane sekwencyjnie: najpierw operacja filtrowania, następnie operacja mapowania, a następnie zbieranie wyniku (więcej informacji na temat korzyści płynących z potoków zbierania znajduje się w doskonałym artykule Martina Fowlera ),
możesz łatwo zmienić sposób zbierania wartości, zastępując
Collector
używany. W niektórych przypadkach może być konieczne napisanie własnegoCollector
, ale korzyścią jest to, że można go łatwo ponownie użyć.źródło
Zgadzam się z istniejącymi odpowiedziami, że druga forma jest lepsza, ponieważ nie ma żadnych skutków ubocznych i jest łatwiejsza do zrównoleglenia (wystarczy użyć strumienia równoległego).
Jeśli chodzi o wydajność, wydaje się, że są one równoważne, dopóki nie zaczniesz używać równoległych strumieni. W takim przypadku mapa będzie działać znacznie lepiej. Zobacz poniżej wyniki testu porównawczego mikro :
Nie możesz wzmocnić pierwszego przykładu w ten sam sposób, ponieważ forEach jest metodą terminalną - zwraca void - więc musisz użyć stanowej lambda. Ale to naprawdę zły pomysł, jeśli używasz równoległych strumieni .
Na koniec zauważ, że Twój drugi fragment kodu można napisać w nieco bardziej zwięzły sposób za pomocą odwołań do metod i importu statycznego:
źródło
Jedną z głównych zalet korzystania ze strumieni jest to, że daje możliwość przetwarzania danych w sposób deklaratywny, to znaczy przy użyciu funkcjonalnego stylu programowania. Daje także możliwość wielowątkowości za darmo, co oznacza, że nie trzeba pisać żadnego dodatkowego kodu wielowątkowego, aby strumień był współbieżny.
Zakładając, że odkrywasz ten styl programowania, ponieważ chcesz wykorzystać te zalety, wtedy Twoja pierwsza próbka kodu potencjalnie nie działa, ponieważ
foreach
metoda jest sklasyfikowana jako terminalna (co oznacza, że może wywoływać skutki uboczne).Drugi sposób jest preferowany z punktu widzenia programowania funkcjonalnego, ponieważ funkcja mapy może przyjmować bezstanowe funkcje lambda. Mówiąc dokładniej, lambda przekazywana do funkcji mapy powinna być
ArrayList
.).Inną korzyścią z drugiego podejścia jest to, że jeśli strumień jest równoległy, a kolektor jest współbieżny i nieuporządkowany, wówczas te cechy mogą dostarczyć użytecznych wskazówek dotyczących operacji redukcji w celu jednoczesnego zbierania.
źródło
Jeśli korzystasz z kolekcji Eclipse , możesz użyć tej
collectIf()
metody.Ocenia z niecierpliwością i powinien być nieco szybszy niż przy użyciu Strumienia.
Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.
źródło
Wolę drugi sposób.
Jeśli użyjesz pierwszego sposobu, jeśli zdecydujesz się użyć strumienia równoległego w celu poprawy wydajności, nie będziesz mieć kontroli nad kolejnością dodawania elementów do listy wyników
forEach
.Podczas korzystania
toList
z interfejsu API Streams zachowa kolejność, nawet jeśli używasz strumienia równoległego.źródło
forEachOrdered
zamiast tego,forEach
gdyby chciał użyć strumienia równoległego, ale nadal zachował porządek. Ale jako dokumentacjaforEach
stanów, zachowanie porządku spotkań poświęca korzyść z równoległości. Podejrzewam, że tak też jest w tym przypadkutoList
.Istnieje trzecia opcja - użycie
stream().toArray()
- zobacz komentarze poniżej, dlaczego stream nie ma metody toList . Okazuje się, że jest wolniejszy niż forEach () lub collect () i mniej wyrazisty. Może być zoptymalizowany w późniejszych wersjach JDK, więc dodaj go tutaj na wszelki wypadek.zarozumiały
List<String>
z testem mikro-mikro, wpisami 1M, zerami 20% i prostą transformacją w doSomething ()
wyniki są
równolegle:
sekwencyjny:
równolegle bez zer i filtru (więc strumień jest
SIZED
): toArrays ma najlepszą wydajność w takim przypadku i.forEach()
kończy się niepowodzeniem z „indexOutOfBounds” na odbiorniku ArrayList, musiał zastąpić przez.forEachOrdered()
źródło
Może być metoda 3.
Zawsze wolę oddzielić logikę.
źródło
W przypadku korzystania 3-ty Pary bibliotekami jest ok Cyklop reagują definiuje Lazy rozszerzyć zbiory z tej funkcji wbudowanych w. Na przykład moglibyśmy po prostu napisać
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));
myFinalList nie jest oceniany aż do pierwszego dostępu (i tam po zmaterializowaniu listy i buforowaniu i ponownym użyciu).
[Ujawnienie Jestem wiodącym twórcą Cyclops-reag]
źródło