Menu

Stream API w Javie: filter, map, collect i reduce

Jak przetwarzać kolekcje przez Stream API w Javie: filter, map, sorted, collect, count i reduce, czyli czytelne potoki zamiast ręcznie pisanych pętli.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Potoki zamiast pętli

Strumień (stream) to potok do przetwarzania sekwencji wartości. Zaczynasz od źródła, zwykle kolekcji, łączysz operacje w rodzaju "zostaw tylko te", "przekształć każdy", "posortuj je", a na końcu zbierasz wynik. Zamiast pisać pętlę z tymczasową listą i if w środku, opisujesz, czego chcesz, a przechodzenie po elementach zostawiasz strumieniowi.

Skoro znasz już lambdy, strumienie wskakują na swoje miejsce: każda operacja przyjmuje lambdę (albo referencję do metody) opisującą pracę dla jednego elementu.

Czytaj od góry do dołu: weź imiona, zostaw długie, zamień na wielkie litery, zbierz do listy. Oryginalna lista names nigdy nie jest zmieniana: strumień tworzy nowy wynik, a źródło zostawia w spokoju.

Budowa strumienia

Każdy potok ma trzy części:

  • Źródło: list.stream(), Arrays.stream(array) albo Stream.of(a, b, c).
  • Zero lub więcej operacji pośrednich: filter, map, sorted, distinct, limit. Każda zwraca kolejny strumień, więc można je łączyć w łańcuch.
  • Dokładnie jedna operacja końcowa: collect, count, forEach, reduce, findFirst. Uruchamia pracę i daje wynik (albo efekt uboczny).

Ten podział ma znaczenie z powodu jednej reguły, która na początku zaskakuje każdego.

Operacje pośrednie są leniwe

Operacje pośrednie same z siebie nic nie robią. Tylko zapisują, co ma się stać. Potok uruchamia się dopiero wtedy, gdy operacja końcowa poprosi o wynik.

Uruchom to: lambda z map nigdy się nie wykonuje. Dodaj operację końcową, a cały łańcuch ożywa:

Ta leniwość to nie dziwactwo, z którym trzeba walczyć: pozwala strumieniowi łączyć operacje i pomijać niepotrzebną pracę. Oznacza jednak, że potok bez operacji końcowej nic nie robi, co jest częstym błędem typu "dlaczego nic się nie dzieje?".

filter i map: zostaw i przekształć

Te dwie operacje dźwigają większość pracy. filter przyjmuje Predicate (lambdę zwracającą boolean) i zostawia elementy, które przejdą test. map przyjmuje Function i zastępuje każdy element wynikiem jej zastosowania.

Zwróć uwagę, że map może zmienić typ elementów: tutaj strumień String staje się strumieniem Integer. String::length to referencja do metody, czyli skrót lambdy w -> w.length().

Operacje końcowe dają wynik

Gdy strumień ma już właściwy kształt, operacja końcowa zamienia go w coś konkretnego:

Popularne operacje końcowe: collect (zebranie do listy, zbioru lub mapy), count, forEach, anyMatch / allMatch / noneMatch, findFirst, min / max i reduce. Po wykonaniu operacji końcowej strumień jest zużyty i nie da się go użyć ponownie. Wywołaj jeszcze raz list.stream(), żeby dostać nowy potok.

Zbieranie wyników

collect z Collectors to główne narzędzie do budowania wyniku. Najczęściej używa się Collectors.toList():

Collectors daje też toSet(), joining(...), groupingBy(...) i counting(). Od Javy 16 możesz zastąpić collect(Collectors.toList()) krótszym .toList() (zwraca listę niemodyfikowalną):

List<String> result = names.stream().map(String::toUpperCase).toList();

sorted, distinct i limit

Te operacje pośrednie zmieniają kształt strumienia przed zebraniem wyniku:

sorted() bez argumentu używa porządku naturalnego; aby ustalić własną kolejność, przekaż Comparator (tutaj lambdę). Comparator.reverseOrder() to czytelniejszy sposób sortowania malejąco.

reduce: zwijanie strumienia do jednej wartości

Gdy trzeba połączyć wszystkie elementy w jeden wynik, np. sumę, iloczyn albo najdłuższy napis, ogólnym narzędziem jest reduce. Podajesz mu wartość początkową i funkcję, która łączy dwie wartości:

Do zwykłych sum i średnich czytelniejsze są strumienie wyspecjalizowane: nums.stream().mapToInt(Integer::intValue).sum(). Po reduce sięgaj, gdy nie ma gotowej funkcji agregującej.

Strumienie nie zastępują każdej pętli

Strumienie błyszczą, gdy przekształcasz kolekcję w wynik. Nie są automatycznie szybsze od pętli i stają się niewygodne, gdy trzeba zmieniać zewnętrzny stan albo przerywać pętlę w zawiły sposób. Dobra zasada: jeśli potok czyta się jak jedno jasne zdanie, użyj strumienia; jeśli sięgasz po współdzielony licznik albo index, zwykła pętla for jest uczciwym i dobrym wyborem.

Pamiętaj też, że strumień jest jednorazowy. Ponowne działanie na nim rzuca wyjątek:

Stream<String> s = names.stream();
s.forEach(System.out::println);
s.count();   // IllegalStateException: stream has already been operated upon

Dalej: Optional

Kilka operacji końcowych, takich jak findFirst, min, max czy reduce bez wartości początkowej, może nic nie znaleźć, więc nie zwracają gołej wartości. Zwracają Optional, kontener Javy na "może wartość, a może nic", który wreszcie daje czystą alternatywę dla zwracania null. O nim jest następna strona.

Najczęściej zadawane pytania

Czym jest strumień (stream) w Javie?

Strumień to potok do przetwarzania sekwencji elementów, zwykle pochodzących z kolekcji, przez łańcuch operacji takich jak filter, map i sorted, zakończony operacją końcową, np. collect albo count. Nie przechowuje danych i nie zmienia źródła; opisuje obliczenie. Tworzysz go przez list.stream() i czytasz łańcuch od góry do dołu jak przepis.

Jak zamienić strumień z powrotem na listę w Javie?

Zakończ potok kolektorem końcowym: list.stream().filter(...).collect(Collectors.toList()). Od Javy 16 możesz użyć krótszego .toList(), które zwraca listę niemodyfikowalną. Bez operacji końcowej nic się nie wykona, bo operacje pośrednie, takie jak filter i map, są leniwe.

Kiedy używać strumienia zamiast pętli for?

Sięgaj po strumień, gdy przekształcasz albo filtrujesz kolekcję do wyniku: potok czyta się jak jedno jasne zdanie ("weź imiona, zostaw długie, zamień na wielkie litery, zbierz do listy"). Zostań przy zwykłej pętli for, gdy musisz zmieniać zewnętrzny stan, przerywać pętlę w skomplikowany sposób albo logika jest prostsza jako kroki imperatywne. Strumienie służą czytelności, a nie surowej szybkości.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ