Lambda izrazi i Stream API u Javi

Lekcija 16 od 16 · Java

Lambda izraz je kratak zapis funkcije koja se prosleđuje kao podatak – na primer, pravilo za poređenje ili uslov za filtriranje. Stream API koristi lambde da se obrada kolekcije zapiše kao niz koraka: izdvoj, preračunaj, sortiraj, saberi. Ovaj stil se mnogo koristi u Spring Boot aplikacijama.

Pre nego što počneš: potrebno predznanje – liste, mape i skupovi, interfejsi.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u Javi (Java 17 ili novija); svaki je preveden i pokrenut i daje navedeni izlaz.

Lambda izraz

Lambda ima parametre, strelicu -> i telo: x -> x * x je funkcija koja vraća kvadrat. Tip lambde je funkcijski interfejs – interfejs sa jednom apstraktnom metodom:

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Function<Integer, Integer> kvadrat = x -> x * x;          // ulaz -> izlaz
        Predicate<String> dugacka = s -> s.length() > 5;          // ulaz -> true/false
        BiFunction<Integer, Integer, Integer> zbir = (a, b) -> a + b;
        Runnable pozdrav = () -> System.out.println("Zdravo!");   // bez parametara

        System.out.println(kvadrat.apply(7));
        System.out.println(dugacka.test("Java") + " " + dugacka.test("Programiranje"));
        System.out.println(zbir.apply(3, 4));
        pozdrav.run();

        // lambda sa više naredbi: vitičaste zagrade i return
        Function<Integer, String> opis = n -> {
            if (n % 2 == 0) return n + " je paran";
            return n + " je neparan";
        };
        System.out.println(opis.apply(15));
    }
}
InterfejsMetodaPrimer
Function<T, R>R apply(T t)s -> s.length()
Predicate<T>boolean test(T t)x -> x > 0
Consumer<T>void accept(T t)x -> System.out.println(x)
Supplier<T>T get()() -> new ArrayList<>()
Comparator<T>int compare(T a, T b)(a, b) -> a - b

Reference na metode

Kada lambda samo poziva postojeću metodu, piše se kraće Klasa::metoda:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> imena = new ArrayList<>(List.of("marko", "Ana", "iva", "Luka"));
        imena.sort(String::compareToIgnoreCase);      // (a, b) -> a.compareToIgnoreCase(b)
        imena.forEach(System.out::println);           // x -> System.out.println(x)
        imena.replaceAll(String::toUpperCase);        // s -> s.toUpperCase()
        System.out.println(imena);
    }
}

Stream: filter, map, sorted, collect

Stream je niz podataka kroz koji elementi „protiču“ kroz korake obrade. Koraci se nadovezuju tačkom, a na kraju stoji završna operacija (collect, count, sum, forEach…):

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> brojevi = List.of(7, 2, 9, 4, 12, 5, 8, 3);

        List<Integer> kvadratiParnih = brojevi.stream()
                .filter(x -> x % 2 == 0)       // zadrži parne: 2, 4, 12, 8
                .map(x -> x * x)               // kvadriraj: 4, 16, 144, 64
                .sorted()                      // sortiraj
                .collect(Collectors.toList());
        System.out.println(kvadratiParnih);

        long vecihOd5 = brojevi.stream().filter(x -> x > 5).count();
        int zbir = brojevi.stream().mapToInt(Integer::intValue).sum();
        double prosek = brojevi.stream().mapToInt(x -> x).average().orElse(0);
        int max = brojevi.stream().max(Integer::compare).get();
        System.out.println(vecihOd5 + " " + zbir + " " + prosek + " " + max);

        String spojeno = brojevi.stream().limit(3).map(String::valueOf).collect(Collectors.joining(" + "));
        System.out.println(spojeno);
    }
}

Stream ne menja originalnu listu – pravi nov rezultat. Jedan stream se može upotrebiti samo jednom.

Stream nad listom objekata

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class Main {
    record Proizvod(String naziv, String kategorija, double cena) { }

    public static void main(String[] args) {
        List<Proizvod> p = List.of(
                new Proizvod("Sveska", "skola", 120),
                new Proizvod("Olovka", "skola", 45),
                new Proizvod("Lopta", "sport", 1500),
                new Proizvod("Ranac", "skola", 2900),
                new Proizvod("Reket", "sport", 3200));

        // nazivi proizvoda jeftinijih od 2000, sortirani po ceni
        List<String> jeftini = p.stream()
                .filter(x -> x.cena() < 2000)
                .sorted(Comparator.comparingDouble(Proizvod::cena))
                .map(Proizvod::naziv)
                .toList();
        System.out.println(jeftini);

        // ukupna cena po kategoriji
        Map<String, Double> poKategoriji = p.stream()
                .collect(Collectors.groupingBy(Proizvod::kategorija, TreeMap::new,
                        Collectors.summingDouble(Proizvod::cena)));
        System.out.println(poKategoriji);

        // najskuplji proizvod
        p.stream().max(Comparator.comparingDouble(Proizvod::cena))
                .ifPresent(x -> System.out.println("Najskuplji: " + x.naziv()));
    }
}

max vraća Optional – „kutiju“ koja može biti prazna (ako je lista prazna). ifPresent izvršava kod samo ako vrednost postoji.

Kada stream, a kada petlja?

Stream je pregledan za izdvajanje, preračunavanje i grupisanje podataka. Obična petlja je jasnija kada se koristi indeks, kada se prekida ranije (break) ili kada se menja više promenljivih odjednom. Na takmičenjima se najčešće koriste petlje (brže su za pisanje i izvršavanje), a u aplikacijama (Spring Boot) – streamovi.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: srednje

Statistika reči

Učitava se jedan red teksta. Koristeći stream, ispisati: broj reči, broj različitih reči (bez obzira na velika i mala slova) i reči duže od 4 slova, malim slovima, bez ponavljanja, abecedno, razdvojene zarezom.

Primer

Ulaz

Java je jezik i Java radi na svakom racunaru jezik

Izlaz

10
8
jezik, racunaru, svakom
ZADATAK 02Težina: teže

Najbolji po razredu

Učitava se n, pa za svakog učenika ime, razred i broj bodova. Za svaki razred (rastuće) ispisati razred: ime bodovi učenika sa najviše bodova; ako ih ima više, abecedno prvog.

Primer

Ulaz

6
Ana 7 88
Marko 8 91
Iva 7 95
Luka 8 91
Sara 6 70
Nina 7 95

Izlaz

6: Sara 70
7: Iva 95
8: Luka 91
Česte greške i saveti
  • Lambda može da koristi lokalnu promenljivu iz okoline samo ako se ta promenljiva posle ne menja (effectively final).
  • Stream se ne može koristiti dva puta – za drugu obradu napravi novi sa lista.stream().
  • map menja svaki element, filter izbacuje elemente – ne mešaj ih.
  • Ne menjaj listu iz koje stream čita unutar forEach.

Proveri znanje

Otvori kviz

Lambda i stream

1. Šta radi x -> x * 2?




2. Koja operacija zadržava samo elemente koji ispunjavaju uslov?




3. Šta je String::length?




4. Da li stream menja originalnu listu?