Lambdy i strumienie w Javie: filter, map, Comparator

Zaktualizowano 22 października 2026

Lambdy i strumienie (streamy) w Javie poszerzają to, co możesz przekazać do metod: zwykle są to wartości takie jak liczby, teksty i listy. Za pomocą lambdy przekazujesz fragment zachowania, na przykład „zwiększ o 50” albo „jest dłuższe niż 4 znaki”. Na tym opierają się strumienie: łańcuchy kroków takich jak filtrowanie, przekształcanie i sumowanie, zupełnie bez pisania własnej pętli.

Lambdy i referencje do metod

Lambda to funkcja bez nazwy. Po lewej stronie strzałki stoją parametry, po prawej wynik:

  • p -> p + 50: jeden parametr
  • (a, b) -> a + b: dwa parametry, w nawiasach
  • w -> { ... return ...; }: kilka linii w nawiasach klamrowych, z return

Lambda pasuje wszędzie tam, gdzie oczekiwany jest interfejs z dokładnie jedną metodą. Najczęstsze z java.util.function:

  • Predicate<T>: warunek, wykonywany przez test(x), zwraca boolean
  • Function<T, R>: zamienia T na R, wykonywana przez apply(x)

Listy (zobacz listy i mapy w Javie) mają metody, które przyjmują lambdę: replaceAll zastępuje każdy element, a removeIf usuwa wszystkie, dla których warunek jest spełniony. Jeśli lambda tylko wywołuje istniejącą metodę, krócej zapiszesz to referencją do metody: String::length oznacza s -> s.length(), a Math::max oznacza (a, b) -> Math.max(a, b).

import java.util.ArrayList;import java.util.List;import java.util.function.Function;import java.util.function.Predicate;
public class Main {    public static void main(String[] args) {        List<Integer> prices = new ArrayList<>(List.of(250, 399, 120));        prices.replaceAll(p -> p + 50);        System.out.println(prices);
        Predicate<String> isLong = w -> w.length() > 4;        System.out.println(isLong.test("Java"));        System.out.println(isLong.test("Lambda"));
        Function<String, String> shout = s -> s.toUpperCase() + "!";        System.out.println(shout.apply("hello"));
        Function<String, Integer> length = String::length;        System.out.println(length.apply("stream"));
        List<String> drinks = new ArrayList<>(List.of("tea", "coffee", "juice", "milk"));        drinks.removeIf(d -> d.length() < 4);        System.out.println(drinks);    }}

Strumienie

list.stream() zamienia listę w strumień elementów, które przepływają przez niego jeden po drugim. Kolejne kroki łączysz kropkami:

  • filter(b -> ...) zostawia tylko elementy, dla których warunek jest spełniony
  • map(Book::title) przekształca każdy element
  • distinct() usuwa duplikaty, sorted() sortuje

Na końcu stoi krok, który daje wynik:

  • toList() zbiera elementy do nowej listy
  • mapToInt(...), a potem sum() sumują
  • reduce(start, (a, b) -> ...) łączy wszystko w jedną wartość: zaczyna od start i łączy dotychczasowy wynik a z kolejnym elementem b

Pierwotna lista pozostaje przy tym bez zmian.

import java.util.List;
record Book(String title, String genre, int pages) {}
public class Main {    public static void main(String[] args) {        List<Book> books = List.of(                new Book("Dune", "sci-fi", 600),                new Book("Emma", "classic", 470),                new Book("Neuromancer", "sci-fi", 270),                new Book("Persuasion", "classic", 250),                new Book("Hyperion", "sci-fi", 480));
        List<String> longBooks = books.stream()                .filter(b -> b.pages() > 400)                .map(Book::title)                .toList();        System.out.println(longBooks);
        int totalPages = books.stream().mapToInt(Book::pages).sum();        System.out.println("Pages: " + totalPages);
        int maxPages = books.stream().map(Book::pages).reduce(0, Math::max);        System.out.println("Longest: " + maxPages);
        List<String> genres = books.stream()                .map(Book::genre)                .distinct()                .sorted()                .toList();        System.out.println(genres);    }}

Comparator i grupowanie

Dzięki Comparable (zobacz dziedziczenie i interfejsy w Javie) klasa ma jedną stałą kolejność sortowania. Comparator to reguła sortowania, którą przekazujesz z zewnątrz:

  • Comparator.comparing(Player::points) sortuje rosnąco według punktów
  • .reversed() odwraca kolejność
  • .thenComparing(Player::name) rozstrzyga remisy

Przekazujesz go do stream().sorted(...) albo do list.sort(...).

collect(Collectors.groupingBy(Player::team, ...)) dzieli elementy na grupy i zwraca mapę: drużyna → wynik. Drugi kolektor określa, co powstaje dla każdej grupy, na przykład Collectors.summingInt(...) dla sumy albo Collectors.counting() dla liczby elementów (jako Long).

import java.util.Comparator;import java.util.List;import java.util.Map;import java.util.stream.Collectors;
record Player(String name, String team, int points) {}
public class Main {    public static void main(String[] args) {        List<Player> players = List.of(                new Player("Mia", "red", 30),                new Player("Leo", "blue", 45),                new Player("Ana", "red", 45),                new Player("Ben", "blue", 20));
        List<Player> ranking = players.stream()                .sorted(Comparator.comparing(Player::points).reversed()                        .thenComparing(Player::name))                .toList();        for (Player p : ranking) {            System.out.println(p.name() + " " + p.points());        }
        Map<String, Integer> perTeam = players.stream()                .collect(Collectors.groupingBy(Player::team,                        Collectors.summingInt(Player::points)));        System.out.println("red: " + perTeam.get("red"));        System.out.println("blue: " + perTeam.get("blue"));
        Map<String, Long> sizes = players.stream()                .collect(Collectors.groupingBy(Player::team, Collectors.counting()));        System.out.println("players in red: " + sizes.get("red"));    }}

Typowe błędy

  • Zmiana zmiennej lokalnej w lambdzie

    Lambda może czytać zmienne lokalne z zewnątrz, ale nie może ich zmieniać:

    int sum = 0;prices.forEach(p -> sum += p);// local variables referenced from a lambda expression// must be final or effectively final

    Do sumowania służy mapToInt(...).sum() albo zwykła pętla.

  • reversed() w złym miejscu

    reversed() odwraca wszystko, co stoi przed nim. Dlatego comparing(Player::points).thenComparing(Player::name).reversed() sortuje od tyłu także imiona. Jeśli reversed() stoi zaraz po comparing(...), dotyczy tylko punktów.

  • Zmiana wyniku toList()

    Listy z toList() i z List.of(...) nie da się zmienić. add kończy się błędem UnsupportedOperationException. Jeśli potrzebujesz listy, którą można zmieniać, skopiuj ją: new ArrayList<>(list).

  • Strumień zużywa się po jednym przejściu

    Strumienia możesz użyć tylko raz. Jeśli poprosisz ten sam strumień o drugi wynik, dostaniesz IllegalStateException. Do drugiego obliczenia po prostu napisz jeszcze raz list.stream().

Zadania z tego tematu

Przećwicz to, czego się nauczyłeś, od razu w przeglądarce. Testy natychmiast sprawdzają twoje rozwiązanie.

Powiązane artykuły