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 nawiasachw -> { ... return ...; }: kilka linii w nawiasach klamrowych, zreturn
Lambda pasuje wszędzie tam, gdzie oczekiwany jest interfejs z dokładnie jedną metodą. Najczęstsze z java.util.function:
Predicate<T>: warunek, wykonywany przeztest(x), zwracabooleanFunction<T, R>: zamieniaTnaR, wykonywana przezapply(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łnionymap(Book::title)przekształca każdy elementdistinct()usuwa duplikaty,sorted()sortuje
Na końcu stoi krok, który daje wynik:
toList()zbiera elementy do nowej listymapToInt(...), a potemsum()sumująreduce(start, (a, b) -> ...)łączy wszystko w jedną wartość: zaczyna odstarti łączy dotychczasowy wynikaz kolejnym elementemb
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 finalDo sumowania służy
mapToInt(...).sum()albo zwykła pętla.reversed() w złym miejscu
reversed()odwraca wszystko, co stoi przed nim. Dlategocomparing(Player::points).thenComparing(Player::name).reversed()sortuje od tyłu także imiona. Jeślireversed()stoi zaraz pocomparing(...), dotyczy tylko punktów.Zmiana wyniku toList()
Listy z
toList()i zList.of(...)nie da się zmienić.addkończy się błędemUnsupportedOperationException. 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 razlist.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
- Listy i mapyListy i mapy w Javie w prosty sposób: ArrayList, HashSet i HashMap z przykładami. Do tego typowe błędy, np. List<int> i remove na liczbach.Czytaj artykuł
- Generyki i nowoczesna JavaTypy generyczne w Javie w prosty sposób: klasy i metody generyczne, var, instanceof, Optional i sealed z przykładami. Do tego typowe błędy, np. Box<int>.Czytaj artykuł
- Dziedziczenie i interfejsyDziedziczenie w Javie w prosty sposób: extends, super, @Override, klasy abstrakcyjne, interfejsy i Comparable z przykładami. Do tego typowe błędy.Czytaj artykuł