CodeGym /Kursy /JAVA 25 SELF /Użycie wyrażeń lambda w kolekcjach i strumieniach

Użycie wyrażeń lambda w kolekcjach i strumieniach

JAVA 25 SELF
Poziom 48, Lekcja 1
Dostępny

1. Wyrażenia lambda i kolekcje

Przypomnijmy, jak wyglądało przetwarzanie kolekcji przed pojawieniem się wyrażeń lambda. Załóżmy, że mamy listę napisów i chcemy je wypisać na ekran:

List<String> list = Arrays.asList("kot", "pies", "jeż");

for (String s : list) {
    System.out.println(s);
}

To proste, ale jeśli chcemy na przykład usunąć z listy wszystkie puste napisy, trzeba pisać pętlę z warunkiem, a czasem użyć iteratora (w przeciwnym razie będzie ConcurrentModificationException). Albo, powiedzmy, sortowanie po długości napisu:

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Nawet w tak prostym zadaniu — już 5 linii kodu i masa „szumu” nawiasów. Jak to zoptymalizować? Odpowiedź już znacie: wyrażenia lambda.

Zastosowanie wyrażeń lambda w metodach kolekcji

Od Java 8 interfejsy kolekcji otrzymały nowe metody, które przyjmują interfejsy funkcyjne — co oznacza, że możemy przekazywać do nich wyrażenia lambda. Oto najpopularniejsze z nich:

  • forEach(Consumer<T> action)
  • removeIf(Predicate<T> filter)
  • sort(Comparator<T> c)
  • replaceAll(UnaryOperator<T> operator)

Przykład: forEach

Wypisać wszystkie elementy listy na ekran (stary sposób):

for (String s : list) {
    System.out.println(s);
}

Teraz — z lambdą:

list.forEach(s -> System.out.println(s));

Albo jeszcze krócej, jeśli chcesz pobawić się w „guru Javy”:

list.forEach(System.out::println); // referencja do metody, omówimy później

Przykład: removeIf

Usunąć wszystkie puste napisy z listy:

List<String> animals = new ArrayList<>(Arrays.asList("kot", "", "pies", "jeż", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [kot, pies, jeż]

Przykład: sort

Sortowanie listy po długości napisu:

List<String> animals = Arrays.asList("kot", "pies", "jeż", "słoń");

animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [kot, jeż, pies, słoń]

Przykład: replaceAll

Przekształcić wszystkie napisy na wielkie litery:

List<String> animals = new ArrayList<>(Arrays.asList("kot", "pies", "jeż"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [KOT, PIES, JEŻ]

2. Stream API i wyrażenia lambda

Wraz z wydaniem Java 8 pojawiło się Stream API — potężne narzędzie do przetwarzania kolekcji w stylu funkcyjnym. Strumienie pozwalają filtrować, przekształcać, sortować i zbierać kolekcje za pomocą łańcuchów metod. I wszystkie te metody przyjmują wyrażenia lambda!

Ważne: Pełne omówienie Stream API będzie później, teraz — tylko podstawowe przykłady dla zrozumienia roli lambd.

Przykład: filtrowanie

Zostawić tylko napisy dłuższe niż 3 znaki:

List<String> animals = Arrays.asList("kot", "słoń", "jeż", "krokodyl");
animals.stream()
       .filter(s -> s.length() > 3)
       .forEach(System.out::println); // słoń, krokodyl

Przykład: przekształcenie (map)

Zamienić wszystkie napisy na wielkie litery:

List<String> animals = Arrays.asList("kot", "słoń", "jeż");
List<String> upper = animals.stream()
                            .map(s -> s.toUpperCase())
                            .collect(Collectors.toList());
System.out.println(upper); // [KOT, SŁOŃ, JEŻ]

Przykład: sortowanie

Uzyskać listę posortowaną po długości (nie zmieniając oryginalnej):

List<String> animals = Arrays.asList("kot", "słoń", "jeż", "krokodyl");
List<String> sorted = animals.stream()
                             .sorted((a, b) -> a.length() - b.length())
                             .collect(Collectors.toList());
System.out.println(sorted); // [kot, jeż, słoń, krokodyl]

3. Porównanie z klasami anonimowymi

Porównajmy, jak wyglądałby ten sam kod z klasą anonimową i z lambdą.

Sortowanie po długości napisu

Klasa anonimowa:

animals.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Wyrażenie lambda:

animals.sort((a, b) -> a.length() - b.length());

Wniosek:
Wyrażenie lambda oszczędza mnóstwo linii i czyni kod bardziej czytelnym. Mniej nawiasów, mniej szumu — więcej sedna!

Usuwanie pustych napisów

Klasa anonimowa:

animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty();
    }
});

Wyrażenie lambda:

animals.removeIf(s -> s.isEmpty());

4. Praktyka: krótkie zadania z wyrażeniami lambda

Spróbujmy w praktyce zastosować wyrażenia lambda w mini-programie do pracy z listą użytkowników.

Przykład 1: Filtrowanie użytkowników po wieku

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

List<User> users = Arrays.asList(
    new User("Alisa", 17),
    new User("Bob", 25),
    new User("Charli", 15)
);

users.stream()
     .filter(u -> u.age >= 18)
     .forEach(System.out::println); // Bob (25)

Przykład 2: Sortowanie użytkowników po imieniu

List<User> users = Arrays.asList(
    new User("Alisa", 17),
    new User("Bob", 25),
    new User("Charli", 15)
);

users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alisa (17), Bob (25), Charli (15)]

Przykład 3: Przekształcenie listy użytkowników w listę imion

List<String> names = users.stream()
                          .map(u -> u.name)
                          .collect(Collectors.toList());
System.out.println(names); // [Alisa, Bob, Charli]

Przykład 4: Usunąć wszystkich niepełnoletnich

List<User> users = new ArrayList<>(Arrays.asList(
    new User("Alisa", 17),
    new User("Bob", 25),
    new User("Charli", 15)
));

users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]

5. Przydatne niuanse

Cechy: zasięg i „efektywnie final” zmienne

Wyrażenie lambda może używać zmiennych z metody zewnętrznej, ale tylko jeśli są final lub „efektywnie final” (czyli nie są zmieniane po inicjalizacji).

int minAge = 18;
users.stream()
     .filter(u -> u.age >= minAge)
     .forEach(System.out::println);

Jeśli spróbujesz zmienić minAge po użyciu w lambdzie — kompilator zgłosi błąd.

Tabela: podstawowe metody kolekcji i strumieni z wyrażeniami lambda

Metoda kolekcji/strumienia Co robi Typ wyrażenia lambda Przykład
forEach(Consumer<T>)
Dla każdego elementu
x -> ...
list.forEach(s -> ...)
removeIf(Predicate<T>)
Usuwa elementy spełniające warunek
x -> ...
list.removeIf(s -> ...)
sort(Comparator<T>)
Sortuje elementy
(a, b) -> ...
list.sort((a, b) -> ...)
replaceAll(UnaryOperator<T>)
Zastępuje każdy element
x -> ...
list.replaceAll(s -> ...)
filter(Predicate<T>)
Filtruje strumień
x -> ...
stream.filter(s -> ...)
map(Function<T, R>)
Przekształca elementy
x -> ...
stream.map(s -> ...)
forEach(Consumer<T>)
Iteracja po strumieniu
x -> ...
stream.forEach(s -> ...)
sorted(Comparator<T>)
Sortowanie w strumieniu
(a, b) -> ...
stream.sorted((a, b) -> ...)

7. Typowe błędy

Błąd nr 1: Lambda jest zbyt długa. Jeśli w wyrażeniu lambda masz już 5 linii kodu, warunki, pętle i try-catch — najpewniej warto wynieść ten kod do osobnej metody. Lambdy są dobre do krótkiej logiki.

Błąd nr 2: Używanie zmiennych modyfikowalnych. Jeśli próbujesz wewnątrz lambdy zmienić zmienną z metody zewnętrznej (na przykład licznik), kompilator na to nie pozwoli. Zmienna musi być final albo efektywnie final.

Błąd nr 3: Zapominanie, że metody kolekcji/strumieni nie zawsze zmieniają oryginalną kolekcję. Na przykład stream().filter(...) nie zmienia listy źródłowej, lecz zwraca nowy strumień. Jeśli chcesz otrzymać kolekcję — użyj collect(Collectors.toList()).

Błąd nr 4: Wyrażenie lambda nie pasuje typem. Jeśli metoda przyjmuje na przykład Comparator<T>, a próbujesz przekazać lambdę z jednym parametrem (zamiast dwóch) — będzie błąd kompilacji.

Błąd nr 5: Utrata czytelności przy zagnieżdżonych lambdach. Jeśli masz łańcuch z map, filter, forEach, a w każdej lambdzie kolejna lambda — kod staje się nieczytelny. W takich przypadkach lepiej podzielić wyrażenia na osobne kroki albo wydzielić części do metod.

1
Zadanie
JAVA 25 SELF, poziom 48, lekcja 1
Niedostępne
System "KRZYCZĄCYCH" Produktów 📢
System "KRZYCZĄCYCH" Produktów 📢
1
Zadanie
JAVA 25 SELF, poziom 48, lekcja 1
Niedostępne
Ekspedycja "Wielkie Wody" 🗺️
Ekspedycja "Wielkie Wody" 🗺️
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION