Kiedy użyjesz collect()
vs reduce()
? Czy ktoś ma dobre, konkretne przykłady, kiedy zdecydowanie lepiej jest iść w jedną lub drugą stronę?
Javadoc wspomina, że metoda collect () jest modyfikowalną redukcją .
Biorąc pod uwagę, że jest to redukcja zmienna, zakładam, że wymaga synchronizacji (wewnętrznej), co z kolei może mieć negatywny wpływ na wydajność. Przypuszczalnie reduce()
jest łatwiejszy do zrównoleglenia kosztem konieczności tworzenia nowej struktury danych do zwrotu po każdym kroku redukcji.
Powyższe stwierdzenia są jednak domysłami i chciałbym, aby ekspert zabrał tutaj głos.
java
java-8
java-stream
jimhooker2002
źródło
źródło
Odpowiedzi:
reduce
jest operacją „ zwijania ”, stosuje operator binarny do każdego elementu w strumieniu, gdzie pierwszy argument operatora jest wartością zwracaną przez poprzednią aplikację, a drugi argument jest bieżącym elementem strumienia.collect
jest operacją agregacji, w której tworzona jest „kolekcja”, a każdy element jest „dodawany” do tej kolekcji. Zbiory w różnych częściach strumienia są następnie sumowane.Dokument powiązany daje powód mający dwa różne podejścia:
Chodzi więc o to, że zrównoleglenie jest takie samo w obu przypadkach, ale w tym
reduce
przypadku stosujemy funkcję do samych elementów strumienia. Wcollect
przypadku, gdy zastosujemy funkcję do zmiennego kontenera.źródło
int
jest niezmienna, więc nie można łatwo użyć operacji zbierania. Możesz zrobić nieprzyzwoity hack, taki jak użycieAtomicInteger
niestandardowego lub jakiegoś niestandardowego,IntWrapper
ale dlaczego miałbyś to zrobić? Operacja składania różni się po prostu od operacji zbierania.reduce
metoda, w której można zwrócić obiekty o typie innym niż elementy strumienia.Powód jest prosty:
collect()
może działać tylko ze zmiennymi obiektami wyników.reduce()
jest przeznaczony do pracy z niezmiennymi obiektami wynikowymi.reduce()
Przykład „ z niezmiennym”collect()
Przykład „ ze zmienną”Na przykład, jeśli chcesz ręcznie obliczyć sumę za pomocą
collect()
nie może pracować zBigDecimal
ale tylkoMutableInt
zorg.apache.commons.lang.mutable
np. Widzieć:Działa to, ponieważ akumulator
container.add(employee.getSalary().intValue());
nie powinien zwracać nowego obiektu z wynikiem, ale zmieniać stan mutowalnegocontainer
typuMutableInt
.Jeśli
BigDecimal
zamiast tegocontainer
chcesz użyćcollect()
metodycontainer.add(employee.getSalary());
, nie możesz użyć metody, ponieważ nie zmieni to,container
ponieważBigDecimal
jest niezmienna. (Poza tymBigDecimal::new
nie zadziała, ponieważBigDecimal
nie ma pustego konstruktora)źródło
Integer
konstruktora (new Integer(6)
), który jest przestarzały w późniejszych wersjach Java.Integer.valueOf(6)
StringBuilder
który jest zmienny. Zobacz: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…Normalna redukcja ma na celu połączenie dwóch niezmiennych wartości, takich jak int, double itp., I utworzenie nowej; to niezmienna redukcja. W przeciwieństwie do tego metoda zbierania została zaprojektowana w celu zmutowania pojemnika w celu zgromadzenia wyniku, który ma przynieść.
Aby zilustrować problem, załóżmy, że chcesz osiągnąć
Collectors.toList()
za pomocą prostej redukcji, takiej jakTo jest odpowiednik
Collectors.toList()
. Jednak w tym przypadku modyfikujesz plikList<Integer>
. Jak wiemyArrayList
, nie jest bezpieczny dla wątków, ani nie można bezpiecznie dodawać / usuwać z niego wartości podczas iteracji, więc otrzymasz wyjątek współbieżnyArrayIndexOutOfBoundsException
lub dowolny inny wyjątek (szczególnie gdy jest uruchamiany równolegle) podczas aktualizowania listy lub sumatora próbuje scalić listy, ponieważ modyfikujesz listę przez gromadzenie (dodawanie) do niej liczb całkowitych. Jeśli chcesz, aby ten wątek był bezpieczny, musisz za każdym razem przekazywać nową listę, która wpłynie negatywnie na wydajność.Natomiast
Collectors.toList()
działa w podobny sposób. Jednak gwarantuje bezpieczeństwo wątków, gdy gromadzisz wartości na liście. Z dokumentacjicollect
metody :Więc odpowiadając na twoje pytanie:
jeśli mają wartości niezmienne, takie jak
ints
,doubles
,Strings
działa wtedy normalnie redukcja dobrze. Jednakże, jeśli masz doreduce
swoich wartości powiedzmy aList
(zmienna struktura danych), musisz użyć mutowalnej redukcji z tącollect
metodą.źródło
x
wątki, każdy „dodając do tożsamości”, a następnie łącząc się razem. Dobry przykład.public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
Próbowałem i nie dostałem wyjątku CCmNiech strumień będzie a <- b <- c <- d
W redukcji,
będziesz miał ((a # b) # c) # d
gdzie # jest interesującą operacją, którą chciałbyś wykonać.
W kolekcji
Twój kolekcjoner będzie miał jakąś strukturę zbierającą K.
K konsumuje. K następnie zużywa b. K następnie zużywa c. K następnie zużywa d.
Na koniec pytasz K, jaki jest ostateczny wynik.
K następnie daje ci go.
źródło
Są one bardzo różne w potencjalnej zużycie pamięci podczas wykonywania. Podczas gdy
collect()
zbiera i umieszcza wszystkie dane w kolekcji,reduce()
jawnie prosi o określenie, w jaki sposób zmniejszyć dane, które przeszły przez strumień.Na przykład, jeśli chcesz odczytać niektóre dane z pliku, przetworzyć je i umieścić w jakiejś bazie danych, możesz otrzymać kod strumienia java podobny do tego:
W tym przypadku używamy,
collect()
aby wymusić na Javie strumieniowe przesyłanie danych i zapisanie wyniku w bazie danych. Bezcollect()
danych nigdy nie jest odczytywane i nigdy nie są przechowywane.Ten kod szczęśliwie generuje
java.lang.OutOfMemoryError: Java heap space
błąd w czasie wykonywania, jeśli rozmiar pliku jest wystarczająco duży lub rozmiar sterty jest wystarczająco mały. Oczywistym powodem jest to, że próbuje ułożyć wszystkie dane, które przeszły przez strumień (i faktycznie zostały już zapisane w bazie danych) w wynikowej kolekcji, co powoduje wysadzenie stosu.Jednakże, jeśli zastąpi
collect()
sięreduce()
- to nie będzie już problemem, jak ten ostatni zmniejszy i odrzucić wszystkie dane, które uczyniły go przez.W przedstawionym przykładzie wystarczy zamienić na
collect()
coś zreduce
:Nie musisz nawet dbać o to, aby obliczenia zależały od
result
języka, ponieważ Java nie jest czystym językiem FP (programowania funkcjonalnego) i nie możesz zoptymalizować danych, które nie są używane na dole strumienia z powodu możliwych efektów ubocznych .źródło
System.out.println (suma);
Funkcja Reduce obsługuje dwa parametry, pierwszy parametr jest poprzednią wartością zwracaną w strumieniu, drugi parametr jest bieżącą wartością obliczaną w strumieniu, sumuje pierwszą wartość i bieżącą wartość jako pierwszą wartość w następnym obliczeniu.
źródło
Według docs
Więc zasadniczo używałbyś
reducing()
tylko wtedy, gdy zostałeś zmuszony do zbierania. Oto kolejny przykład :Zgodnie z tym poradnikiem redukcja jest czasami mniej wydajna
Tak więc tożsamość jest „ponownie wykorzystywana” w scenariuszu redukcji, więc
.reduce
jeśli to możliwe , jest nieco bardziej wydajna .źródło