CodeGym /Kursy /JAVA 25 SELF /Wprowadzenie do Stream API: po co są strumienie

Wprowadzenie do Stream API: po co są strumienie

JAVA 25 SELF
Poziom 30 , Lekcja 0
Dostępny

1. Problem podejścia imperatywnego

Zacznijmy od klasycznego zadania. Niech mamy listę użytkowników:

List<User> users = ...; // już zapełniona

Załóżmy, że musimy uzyskać listę imion wszystkich pełnoletnich użytkowników (od 18 lat). Jak robiliśmy to wcześniej?

List<String> names = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        names.add(user.getName());
    }
}

Na pierwszy rzut oka wszystko jest proste, ale zwróć uwagę na „techniczne” kroki: zadeklarować nową listę na wynik, przejść po wszystkich elementach, sprawdzić warunek, dodać wynik.

Jeśli zadanie się komplikuje — na przykład trzeba uzyskać alfabetycznie posortowaną listę unikalnych adresów e‑mail wszystkich użytkowników, których imiona zaczynają się na literę „A” — kod szybko puchnie: filtrowanie, wyciąganie pól, usuwanie duplikatów, sortowanie, być może konwersje formatu e‑mail itd. W rezultacie zamiast krótkiej, zrozumiałej logiki dostajesz górę boilerplate’u, którą trudno czytać i utrzymywać.

Wady takiego podejścia w dwóch słowach: dużo powtarzalnego kodu, wysokie ryzyko popełnienia błędu (zapomnieć o sortowaniu lub źle obsłużyć duplikaty) oraz trudność w łączeniu operacji. To właśnie nazywa się stylem imperatywnym: krok po kroku opisujesz komputerowi, jak wykonać zadanie, a nie co chcesz otrzymać.

2. Stream API — styl deklaratywny

Wraz z Java 8 pojawił się Stream API — narzędzie, które pozwala pracować z kolekcjami w stylu „co trzeba zrobić”, a nie „jak dokładnie to zrobić”. Takie podejście nazywa się deklaratywnym.

A czym w ogóle jest Stream?
To nie jest osobna kolekcja, lecz raczej strumień danych: sekwencja elementów, które przechodzą przez łańcuch operacji — filtrowanie, przekształcanie, sortowanie, zbieranie wyniku itd. Strumień niczego nie przechowuje, jedynie „przepycha” dane przez linię produkcyjną. Operacje można łączyć niemal jak klocki LEGO: układasz łańcuch i uruchamiasz.

Przykład:

List<String> names = users.stream()                // tworzymy strumień z listy użytkowników
    .filter(u -> u.getAge() >= 18)                 // zostawiamy tylko tych, którzy mają już 18 lat 
    .map(User::getName)                            // przekształcamy User → String (bierzemy imię)
    .collect(Collectors.toList());                 // zbieramy wynik do listy

I to wszystko — w jednym łańcuchu opisaliśmy całe zadanie: „weź użytkowników, odfiltruj po wieku, pobierz imiona, zbierz do listy”. Nie trzeba ręcznie pisać pętli, tworzyć zmiennych tymczasowych ani pilnować, żeby o niczym nie zapomnieć.

Stream API działa jak taśma produkcyjna: definiujesz etapy przetwarzania, a dane same przez nie przechodzą. Estetycznie, zwięźle i czyta się wielokrotnie łatwiej.

3. Zalety Stream API

  • Zwięzłość i czytelność. Porównaj dwa podejścia — imperatywne i deklaratywne — na tym samym zadaniu. Od razu widać, które łatwiej czytać i utrzymywać.
  • Łatwa kompozycja operacji. Operacje łatwo „składać” w łańcuch: filter, map, sorted, collect — i to wszystko w jednej spójnej sekwencji.
  • Mniej błędów. Stream API uwalnia od rutyny: nie trzeba ręcznie zarządzać kolekcjami pośrednimi i pętlami — mniejsze ryzyko, że zapomnisz krok lub popełnisz błąd indeksowania.
  • Możliwość równoległego przetwarzania. Łatwo skalować obróbkę na wiele rdzeni, po prostu wywołując parallelStream() zamiast stream(). Szczegóły — później.
  • Nowoczesny styl programowania. Idee programowania funkcyjnego (kompozycja funkcji, brak jawnych pętli) zwiększają ekspresyjność i są cenione na rynku.

Krótka historia Stream API

Stream API pojawił się w Javie 8 (2014) i był jakościowym skokiem dla języka. Wcześniej każda nietrywialna operacja na kolekcjach wymagała mnóstwa kodu pomocniczego, podczas gdy inne platformy już oferowały podejścia deklaratywne (map, filter, reduce itd.).

Od tego czasu Stream API jest standardem de facto do przetwarzania kolekcji w Javie. Jeśli chcesz pisać nowoczesny kod w Javie — bez niego ani rusz.

4. Zastosowania Stream API

  • Filtrowanie: wybór tylko potrzebnych elementów (np. wszyscy użytkownicy starsi niż 18 lat).
  • Przekształcanie: wyciągnięcie pola lub utworzenie nowego obiektu (np. lista imion użytkowników).
  • Agregacja: policzyć sumę, średnią, liczbę, maksimum/minimum.
  • Sortowanie: uporządkować elementy według potrzebnego kryterium.
  • Grupowanie: podzielić elementy na kategorie.
  • Zbieranie wyniku: zebrać do listy, zbioru, mapy, łańcucha znaków itd.

Przykładowe zadania:

  • Uzyskać listę adresów e‑mail wszystkich użytkowników, których imię zaczyna się na „A”.
  • Policzyć średni wiek użytkowników.
  • Znaleźć pierwszego użytkownika o określonym adresie e‑mail.
  • Zebrać wszystkie unikalne miasta zamieszkania do jednego zbioru.

5. Przydatne niuanse

Jak działa strumień

[User1] --\
[User2] ---|--> [ filter ] --> [ map ] --> [ collect ] --> List<String>
[User3] --/

1. filter — przepuszcza tylko użytkowników spełniających warunek.
2. map — przekształca użytkownika np. na e‑mail.
3. collect — zbiera adresy e‑mail do potrzebnej kolekcji.

Składnia: jak utworzyć strumień

  • Z kolekcji: list.stream() — strumień elementów kolekcji.
  • Z tablicy: Arrays.stream(array)
  • Z pojedynczych wartości: Stream.of("a", "b", "c")

6. Typowe błędy przy przechodzeniu na Stream API

Błąd nr 1: próba modyfikowania kolekcji wewnątrz strumienia. Stream API nie jest przeznaczony do modyfikacji kolekcji źródłowej (np. nie należy wywoływać list.remove() w ciele forEach). Do usuwania użyj na przykład removeIf na kolekcji przed lub po przetwarzaniu.

Błąd nr 2: mylenie kolekcji ze strumieniem. Strumień to nie kolekcja! Strumień „przechodzi” po elementach jeden raz, po czym zostaje zamknięty. Jeśli trzeba ponownie przetworzyć dane — utwórz nowy stream().

Błąd nr 3: zbyt skomplikowane łańcuchy. Nie zamieniaj wyrażenia w „potwora” z kilkunastu operacji. Jeśli łańcuch staje się długi — podziel go na kilka kroków z pośrednimi zmiennymi dla czytelności i łatwiejszego debugowania.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION