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));
}
}
Interfejs
Metoda
Primer
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
Uputstvo
Arrays.stream(red.toLowerCase().split("\\s+")), pa count(), distinct().count() i filter(...).distinct().sorted().collect(Collectors.joining(", ")).
Rešenje (Java)
import java.util.Arrays;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
String red = new Scanner(System.in).nextLine().trim().toLowerCase();
String[] reci = red.split("\\s+");
System.out.println(reci.length);
System.out.println(Arrays.stream(reci).distinct().count());
System.out.println(Arrays.stream(reci)
.filter(r -> r.length() > 4)
.distinct()
.sorted()
.collect(Collectors.joining(", ")));
}
}
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
Uputstvo
Collectors.groupingBy(Ucenik::razred, TreeMap::new, Collectors.minBy(poredak)), gde poredak stavlja na prvo mesto više bodova, pa abecedno ime.
Rešenje (Java)
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class Main {
record Ucenik(String ime, int razred, int bodovi) { }
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
List<Ucenik> u = new ArrayList<>();
for (int i = 0; i < n; i++)
u.add(new Ucenik(sc.next(), sc.nextInt(), sc.nextInt()));
Comparator<Ucenik> poredak = Comparator.comparingInt(Ucenik::bodovi).reversed()
.thenComparing(Ucenik::ime);
Map<Integer, Optional<Ucenik>> najbolji = u.stream()
.collect(Collectors.groupingBy(Ucenik::razred, TreeMap::new, Collectors.minBy(poredak)));
najbolji.forEach((razred, x) ->
System.out.println(razred + ": " + x.get().ime() + " " + x.get().bodovi()));
}
}
Č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?
Podešavanja kolačića
Koristimo kolačiće da bismo vam pružili najbolje moguće iskustvo. Takođe nam omogućavaju da analiziramo ponašanje korisnika kako bismo stalno poboljšavali veb lokaciju za vas.