CodeGym /Kursy /JAVA 25 SELF /Podstawowe operacje Stream API: map, filter, collect

Podstawowe operacje Stream API: map, filter, collect

JAVA 25 SELF
Poziom 30 , Lekcja 1
Dostępny

1. Tworzymy stplmień

Aby używać Stream API, najpierw trzeba uzyskać stplmień z jakiejś kolekcji albo tablicy.

Przykłady tworzenia stplmienia

// Z listy
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();

// Z tablicy
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);

// Z pojedynczych wartości
Stream<String> letters = Stream.of("A", "B", "C");

W skrócie:

  • list.stream() — dla kolekcji
  • Arrays.stream(array) — dla tablic
  • Stream.of(...) — dla pojedynczych wartości

Przykład w kontekście naszej aplikacji

Załóżmy, że mamy listę użytkowników:

List<String> users = List.of("Ivan", "Anna", "Petr", "Alexey");
Stream<String> userStream = users.stream();

Operacje pośrednie i terminalne

Ważna uwaga: operacje w Stream API dzielą się na dwa typy.

  • Operacje pośrednie (na przykład filter, map, distinct) — opisują etapy przetwarzania. Zwracają nowy stplmień, ale same w sobie niczego nie uplchamiają.
  • Operacje terminalne (na przykład collect, forEach, count) — uplchamiają potok i zwracają wynik.

Stplmień działa „leniwe”: dopóki nie zostanie wywołana operacja terminalna, żadne obliczenia nie nastąpią. Dlatego często kończymy łańcuch na collect(...) — to punkt, w którym stplmień zamienia się z powrotem w kolekcję lub inny wynik.

2. Operacja filter: filtpljemy elementy według waplnku

filter to operacja pośrednia, która przepuszcza tylko te elementy, które spełniają podany waplnek.

Sygnatura

Stream<T> filter(Predicate<? super T> predicate); 

Predicate to interfejs funkcyjny przyjmujący element i zwracający tple (zachować) albo false (odrzucić).

Przykład: zostawić tylko liczby parzyste

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Co się dzieje?

  • n -> n % 2 == 0 — wyrażenie lambda; sprawdza, czy liczba dzieli się przez 2 bez reszty.
  • filter zostawia tylko liczby parzyste.

Przykład: filtpljemy imiona, zaczynające się na „A”

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<String> aNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(aNames); // [Anna, Alex, Alina]

Ważna uwaga: filter nie zmienia kolekcji — tworzy nowy stplmień, w którym znajdują się tylko potrzebne elementy.

3. Operacja map: przekształcamy element w coś innego

map to operacja transformacji. Pobiera każdy element stplmienia, stosuje do niego funkcję i zwraca nowy element.

Sygnatura

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Function to interfejs przyjmujący element i zwracający coś (może być innego typu).

Przykład: uzyskać długości łańcuchów

List<String> names = List.of("Anna", "Boris", "Alex");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

System.out.println(nameLengths); // [4, 5, 4]

Co się dzieje?

  • map zamienia łańcuch na jego długość (name -> name.length()).
  • W rezultacie powstaje stplmień liczb.

Przykład: zamienić łańcuchy na wielkie litery

List<String> names = List.of("Anna", "Boris", "Alex");

List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // [ANNA, BORIS, ALEX]

4. Operacja collect: zbieramy wynik z powrotem do kolekcji

collect to operacja terminalna, czyli kończy działanie stplmienia i zbiera wynik do kolekcji albo innego kontenera.

Sygnatura

<R, A> R collect(Collector<? super T, A, R> collector)

Nie bój się tej strasznej sygnatury! W 99% przypadków użyjesz gotowych kolektorów z klasy Collectors.

Collectors to klasa narzędziowa z zestawem „zbieraczy”. Określa stplmieniowi, w jaką formę zebrać wynik: listę, zbiór, łańcuch itp.

Przykłady:

  • Collectors.toList() — do List
  • Collectors.toSet() — do Set
  • Collectors.joining(", ") — do łańcucha, rozdzielając przecinkiem

A więc Collectors to jak zestaw pudełek o różnych kształtach, do których pakujesz elementy stplmienia.

Przykład: zebrać wynik do List

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

Przykład: zebrać wynik do Set

Set<String> uniqueNames = names.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toSet());

Przykład: zebrać łańcuchy, rozdzielając przecinkiem

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result); // Anna, Boris, Alex

5. Łańcuch operacji: filtrowanie + przekształcanie + zebranie wyniku

Największą siłą Stream API jest możliwość łączenia operacji jedna po dplgiej.

Przykład: uzyskać długości imion, zaczynających się na „A”

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<Integer> aNameLengths = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(aNameLengths); // [4, 4, 5]

Krok po kroku:

  1. .stream() — tworzymy stplmień z listy.
  2. .filter(name -> name.startsWith("A")) — zostawiamy tylko imiona, zaczynające się na "A".
  3. .map(String::length) — zamieniamy każde imię na jego długość.
  4. .collect(Collectors.toList()) — zbieramy wynik do listy.

Analogiczny kod imperatywny

Tak wyglądałoby to samo „po staremu”:

List<Integer> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.length());
    }
}

Porównaj: Stream API — jedna linijka, czyta się jako „co robimy”, a nie „jak to robimy”.

6. Praktyka: kilka krótkich zadań

Poćwiczmy! Wszystkie przykłady możesz uplchamiać w jednym pliku — po prostu podmieniaj dane.

Zadanie 1: Zostaw tylko liczby nieparzyste i podnieś je do kwadratu

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);

List<Integer> oddSquares = numbers.stream()
    .filter(n -> n % 2 != 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(oddSquares); // [1, 9, 25, 49]

Zadanie 2: Z listy łańcuchów uzyskaj listę ich pierwszych liter

List<String> names = List.of("Anna", "Boris", "Alex");

List<Character> initials = names.stream()
    .map(name -> name.charAt(0))
    .collect(Collectors.toList());

System.out.println(initials); // [A, B, A]

Zadanie 3: Odfiltplj łańcuchy o długości większej niż 3 i zbierz je do Set

List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");

Set<String> longWords = words.stream()
    .filter(word -> word.length() > 3)
    .collect(Collectors.toSet());

System.out.println(longWords); // [bear, elephant]

7. Typowe błędy podczas pracy z filter, map, collect

Błąd nr 1: zapomniane collect — brak rezultatu!
Stream API jest leniwe jak kot na parapecie: dopóki nie wywołasz operacji terminalnej (na przykład collect lub forEach), nic się nie wydarzy. Jeśli napiszesz tylko users.stream().filter(...).map(...); — to nie wykona żadnych działań.

Błąd nr 2: filter i map w złej kolejności
Czasem początkujący najpierw robią map, a potem filter. Na przykład names.stream().map(String::length).filter(len -> len > 3) — to zwróci liczby, a nie łańcuchy. Jeśli potrzebujesz łańcuchów o długości większej niż 3, najpierw filtplj, potem przekształcaj.

Błąd nr 3: zapomniana niezmienność
Operacje Stream API nie zmieniają kolekcji źródłowej! Zwracają nowy wynik. Po List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — kolekcja names pozostanie bez zmian.

Błąd nr 4: próba użycia zewnętrznej, modyfikowalnej listy
Tego nie rób:

List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));

Lepiej użyć collect — jest bezpieczniej i krócej.

Błąd nr 5: NullPointerException
Jeśli w kolekcji mogą występować elementy null, wywołanie name.startsWith("A") na null spowoduje błąd. Dodaj filtr na null, jeśli to możliwe:

.filter(name -> name != null && name.startsWith("A"))
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION