This shows you the differences between two versions of the page.
|
poo-ca-cd:laboratoare:programare-functionala-lambda-si-streamuri [2025/12/15 03:09] florian_luis.micu [Lambda Expressions – implementare inline] |
poo-ca-cd:laboratoare:programare-functionala-lambda-si-streamuri [2025/12/15 11:44] (current) florian_luis.micu [Obiective] |
||
|---|---|---|---|
| Line 4: | Line 4: | ||
| * Data publicării: 15.12.2025 | * Data publicării: 15.12.2025 | ||
| * Data ultimei modificări: 15.12.2025 | * Data ultimei modificări: 15.12.2025 | ||
| + | * adăugare secțiune despre operații short-circuit. | ||
| + | * adăugare secțiune [Nice to know] despre Optional. | ||
| + | * adăugare secțiune despre primitive streams. | ||
| + | * menționarea boolean chaining când folosim interfețe funcționale de tip Predicate. | ||
| ======Obiective====== | ======Obiective====== | ||
| Line 16: | Line 20: | ||
| * diferențe dintre stilul imperativ și stilul funcțional de programare. | * diferențe dintre stilul imperativ și stilul funcțional de programare. | ||
| * identificarea avantajelor, limitărilor și capcanelor ale abordării funcționale în Java. | * identificarea avantajelor, limitărilor și capcanelor ale abordării funcționale în Java. | ||
| + | |||
| + | Aspectele **bonus** urmărite sunt: | ||
| + | * definirea și folosirea Optional în programarea în stil funcțional. | ||
| + | * paralelizarea codului folosind parallel streams. | ||
| <note warning> | <note warning> | ||
| Line 22: | Line 30: | ||
| </note> | </note> | ||
| - | ======🏷️ Programare funcțională====== | + | ======🔧 Programarea funcțională====== |
| + | Există mai multe **paradigme de programare**, fiecare definind un mod diferit de a structura și exprima logica unui program. Cele mai importante sunt **programarea imperativă**, care descrie execuția pas cu pas, **programarea orientată pe obiecte**, care modelează aplicația prin obiecte ce combină date și comportament, și **programarea funcțională**, care pune accent pe transformări de date, funcții pure și evitarea stării mutabile. | ||
| + | |||
| + | Majoritatea limbajelor moderne sunt **multi-paradigmă**. Java, de exemplu, combină stilul imperativ și orientat pe obiecte cu elemente de programare funcțională (lambda expressions, Stream API), permițând alegerea paradigmei potrivite în funcție de problemă. | ||
| + | |||
| + | <note tip> | ||
| + | Există mai multe tipuri de paradigme de programare: | ||
| + | * programare imperativă (ex. C, Fortran) | ||
| + | * programare orientată pe obiecte (ex. Java, C#, C++, Python) | ||
| + | * programare funcțională (ex. Haskell, Lisp) | ||
| + | * programare declarativă (ex. SQL, HTML) | ||
| + | * programare logică (ex. Prolog) | ||
| + | * programare bazată pe evenimente (ex. JavaScript) | ||
| + | </note> | ||
| =====De ce am programa funcțional în Java?===== | =====De ce am programa funcțional în Java?===== | ||
| Line 341: | Line 362: | ||
| isEven.test(10); // true | isEven.test(10); // true | ||
| </code> | </code> | ||
| + | |||
| + | <note tip> | ||
| + | Predicate este util și pentru înlănțuirea condițiilor boolean prin folosirea metodelor ''and(...)'', ''or(...)'', ''negate(...)''. | ||
| + | <code java> | ||
| + | Predicate<Integer> isPositive = x -> x > 0; | ||
| + | Predicate<Integer> isEven = x -> x % 2 == 0; | ||
| + | |||
| + | Predicate<Integer> positiveAndEven = | ||
| + | isPositive.and(isEven); | ||
| + | |||
| + | System.out.println(positiveAndEven.test(4)); // true | ||
| + | System.out.println(positiveAndEven.test(3)); // false | ||
| + | System.out.println(positiveAndEven.test(-2)); // false | ||
| + | </code> | ||
| + | </note> | ||
| ===Function<T, R> – transformări=== | ===Function<T, R> – transformări=== | ||
| Line 971: | Line 1007: | ||
| </spoiler> | </spoiler> | ||
| + | |||
| + | ====Operații short-circuit==== | ||
| + | |||
| + | Unele operații pot **opri execuția** înainte de final. | ||
| + | |||
| + | Exemple: | ||
| + | * ''anyMatch'' - Verifică dacă există cel puțin un element în stream care respectă o condiție (se oprește la prima potrivire). | ||
| + | * ''findFirst'' - Returnează primul element din stream, respectând ordinea (rezultat: ''Optional<T>'') | ||
| + | * ''findAny'' – Returnează orice element din stream (fără garanție de ordine, optim pentru parallel streams). | ||
| + | * ''limit'' – Restrânge stream-ul la primele N elemente, oprind procesarea după atingerea limitei. | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | boolean hasEven = | ||
| + | numbers.stream() | ||
| + | .peek(x -> System.out.println(x)) | ||
| + | .anyMatch(x -> x % 2 == 0); | ||
| + | </code> | ||
| + | |||
| + | În exemplul de mai sus, stream-ul printează elementele și se oprește **la primul element par**. | ||
| =====Legătura dintre Stream API și Interfețele Funcționale===== | =====Legătura dintre Stream API și Interfețele Funcționale===== | ||
| Line 1041: | Line 1097: | ||
| Fiecare element trece **complet** prin pipeline înainte de următorul. | Fiecare element trece **complet** prin pipeline înainte de următorul. | ||
| </note> | </note> | ||
| + | |||
| + | =====Primitive Streams===== | ||
| + | |||
| + | Stream-urile generice (''Stream<Integer>'') folosesc: | ||
| + | * boxing | ||
| + | * unboxing | ||
| + | |||
| + | Asta înseamnă că lucrul cu ele este **mai lent** și consumă mai multă memorie. | ||
| + | |||
| + | Exemplu folosire boxing: | ||
| + | <code java> | ||
| + | int sum = | ||
| + | numbers.stream() | ||
| + | .map(x -> x) | ||
| + | .reduce(0, Integer::sum); | ||
| + | </code> | ||
| + | |||
| + | Exemplu folosire primitive streams: | ||
| + | <code java> | ||
| + | int sum = | ||
| + | numbers.stream() | ||
| + | .mapToInt(x -> x) | ||
| + | .sum(); | ||
| + | </code> | ||
| + | |||
| + | ====Tipuri de Primitive Streams==== | ||
| + | |||
| + | ^ Tip ^ Clasă ^ | ||
| + | | int | IntStream | | ||
| + | | long | LongStream | | ||
| + | | double | DoubleStream | | ||
| + | |||
| + | ====Metode utile==== | ||
| + | |||
| + | ^ Metodă ^ Ce face ^ Tip returnat ^ Observații importante ^ | ||
| + | | ''mapToInt(...)'' | Transformă un ''Stream<T>'' într-un ''IntStream'' | ''IntStream'' | Evită autoboxing (''Integer'' → ''int'') | | ||
| + | | ''sum()'' | Calculează suma elementelor din ''IntStream'' | ''int'' | Returnează ''0'' dacă stream-ul e gol | | ||
| + | | ''average()'' | Calculează media aritmetică a elementelor | ''OptionalDouble'' | Folosește ''Optional'' pentru a evita ''0/NaN'' | | ||
| + | | ''min()'' | Găsește valoarea minimă din stream | ''OptionalInt'' | Stream-ul poate fi gol | | ||
| + | | ''max()'' | Găsește valoarea maximă din stream | ''OptionalInt'' | Stream-ul poate fi gol | | ||
| + | |||
| + | |||
| + | <note tip> | ||
| + | Pentru calcule numerice, este mai bine să **folosiți primitive streams**. | ||
| + | </note> | ||
| + | |||
| + | =====[Nice to know] Optional<T> – evitarea valorilor null===== | ||
| + | |||
| + | ====Ce este Optional<T>?==== | ||
| + | |||
| + | ''Optional<T>'' este un container care poate: | ||
| + | * să conțină o valoare | ||
| + | * sau să fie gol | ||
| + | |||
| + | Scopul lui este să evite folosirea lui ''null'' și apariția erorilor de tip ''NullPointerException''. | ||
| + | |||
| + | <code java> | ||
| + | Optional<String> opt = Optional.of("hello"); | ||
| + | Optional<String> empty = Optional.empty(); | ||
| + | </code> | ||
| + | |||
| + | ====De ce avem nevoie de Optional?==== | ||
| + | |||
| + | Fără ''Optional'' avem următoarea situație: | ||
| + | <code java> | ||
| + | String name = getName(); | ||
| + | if (name != null) { | ||
| + | System.out.println(name.length()); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | În schimb, cu ''Optional'' putem scrie codul de mai sus astfel: | ||
| + | <code java> | ||
| + | Optional<String> name = getName(); | ||
| + | name.ifPresent(n -> System.out.println(n.length())); | ||
| + | </code> | ||
| + | |||
| + | <note tip> | ||
| + | Prin folosirea ''Optional'', codul: | ||
| + | * este mai sigur | ||
| + | * este mai expresiv | ||
| + | * forțează tratarea cazului „lipsește valoarea” | ||
| + | </note> | ||
| + | |||
| + | ====Metode importante din Optional==== | ||
| + | |||
| + | **1. ''isPresent()''** | ||
| + | |||
| + | <code java> | ||
| + | if (optional.isPresent()) { | ||
| + | System.out.println(optional.get()); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Ce face: | ||
| + | * Verifică dacă ''Optional'' **conține o valoare**. | ||
| + | |||
| + | Problema: | ||
| + | * Duce ușor la **cod de tip null-check mascat** | ||
| + | * Necesită apel ulterior la ''get()'', care poate arunca excepție dacă greșim logica | ||
| + | |||
| + | <note tip> | ||
| + | Această metodă **nu este recomandată** în stil funcțional modern. | ||
| + | </note> | ||
| + | |||
| + | **2. ''ifPresent(Consumer<T>)''** | ||
| + | |||
| + | <code java> | ||
| + | optional.ifPresent(value -> System.out.println(value)); | ||
| + | </code> | ||
| + | |||
| + | Ce face: | ||
| + | * Execută codul doar dacă valoarea există | ||
| + | * Nu necesită ''get()'' | ||
| + | * Evită ''NullPointerException'' | ||
| + | |||
| + | Când se folosește: | ||
| + | * pentru afișare | ||
| + | * logging | ||
| + | * efecte secundare controlate | ||
| + | |||
| + | **3. ''orElse(T other)''** | ||
| + | |||
| + | <code java> | ||
| + | String value = optional.orElse("default"); | ||
| + | </code> | ||
| + | |||
| + | Ce face: | ||
| + | * Returnează valoarea din ''Optional'' | ||
| + | * Dacă este empty, returnează valoarea furnizată (''"default"'') | ||
| + | |||
| + | Când se folosește: | ||
| + | * când valoarea default este **constantă** sau foarte ieftină | ||
| + | |||
| + | <note important> | ||
| + | Argumentul este **evaluat întotdeauna**, chiar dacă ''Optional'' are valoare, deci este important să avem grijă la **apelurile de metodă** folosite ca valoare default: | ||
| + | <code java> | ||
| + | optional.orElse(computeDefault()); // computeDefault() se execută oricum | ||
| + | </code> | ||
| + | </note> | ||
| + | |||
| + | **4. orElseGet(...) (lazy)** | ||
| + | |||
| + | <code java> | ||
| + | String value = optional.orElseGet(() -> computeDefault()); | ||
| + | </code> | ||
| + | |||
| + | Ce face: | ||
| + | * Returnează valoarea din ''Optional'' | ||
| + | * Dacă este empty, apelează ''Supplier-ul'' | ||
| + | |||
| + | Când se folosește: | ||
| + | * când valoarea default: | ||
| + | * este costisitoare | ||
| + | * implică I/O | ||
| + | * creează obiecte | ||
| + | |||
| + | <note important> | ||
| + | Față de metoda ''orElse'', dacă includem ca valoare default un **apel de metodă**, aceasta este apelată **doar dacă este necesar**. | ||
| + | <code java> | ||
| + | // recomandat | ||
| + | optional.orElseGet(this::computeDefault); | ||
| + | </code> | ||
| + | </note> | ||
| + | |||
| + | <note tip> | ||
| + | Această metodă este preferată față de ''orElse(T other)''. | ||
| + | </note> | ||
| + | |||
| + | **5. orElseThrow()** | ||
| + | |||
| + | <code java> | ||
| + | String value = optional.orElseThrow(); | ||
| + | </code> | ||
| + | |||
| + | Ce face: | ||
| + | * Returnează valoarea dacă există | ||
| + | * Aruncă ''NoSuchElementException'' dacă este empty | ||
| + | |||
| + | Când se folosește: | ||
| + | * când lipsa valorii este **o eroare de logică** | ||
| + | * când vrei să **forțezi tratarea cazului empty** | ||
| + | |||
| + | Echivalent logic cu: | ||
| + | <code java> | ||
| + | if (optional.isEmpty()) { | ||
| + | throw new NoSuchElementException(); | ||
| + | } | ||
| + | return optional.get(); | ||
| + | </code> | ||
| + | |||
| + | Varianta cu excepție custom: | ||
| + | <code java> | ||
| + | String value = optional.orElseThrow( | ||
| + | () -> new IllegalStateException("Valoare lipsă") | ||
| + | ); | ||
| + | </code> | ||
| + | |||
| + | ====Optional și Stream API==== | ||
| + | |||
| + | ''Optional'' poate fi cuplat cu Stream API pentru a crea secvențe de cod avansate: | ||
| + | |||
| + | <code java> | ||
| + | Optional<Integer> firstEven = | ||
| + | numbers.stream() | ||
| + | .filter(x -> x % 2 == 0) | ||
| + | .findFirst(); | ||
| + | </code> | ||
| + | |||
| + | În exemplul de mai sus, ''findFirst()'' nu întoarce direct valoarea, ci un ''Optional<T>''. | ||
| + | |||
| + | ==Good practices== | ||
| + | |||
| + | Nu folosiți ''get()'' direct. Dacă sunteți tentați să folosiți ''get()'', probabil metoda potrivită este: | ||
| + | * ''ifPresent'' | ||
| + | * ''orElseGet'' | ||
| + | * ''orElseThrow'' | ||
| + | |||
| + | ====Ce nu este Optional==== | ||
| + | |||
| + | ''Optional'' se folosește **mai ales ca valoare de return**, dar este important să știm că nu este: | ||
| + | * înlocuitor universal pentru toate valorile | ||
| + | * destinat câmpurilor de clasă | ||
| + | * destinat serializării | ||
| + | |||
| + | ====Optional<T> vs. Optional Primitives==== | ||
| + | |||
| + | ===Optional<T>=== | ||
| + | |||
| + | ''Optional<T>'' este varianta **generică**, care funcționează cu **orice tip obiect**. | ||
| + | |||
| + | Caracteristici: | ||
| + | * folosește **tipuri obiect** (''Integer'', ''Double'', ''String'', etc.) | ||
| + | * implică **autoboxing** pentru tipurile primitive | ||
| + | * este flexibil, dar **mai puțin eficient** pentru calcule numerice | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | Optional<Integer> max = | ||
| + | numbers.stream() | ||
| + | .max(Integer::compareTo); | ||
| + | |||
| + | max.ifPresent(System.out::println); | ||
| + | </code> | ||
| + | |||
| + | ===OptionalInt/OptionalLong/OptionalDouble=== | ||
| + | |||
| + | ''OptionalInt'' / ''OptionalLong'' și ''OptionalDouble'' sunt versiuni specializate pentru primitive. | ||
| + | |||
| + | Caracteristici: | ||
| + | * lucrează direct cu ''int'' | ||
| + | * **nu folosește autoboxing** | ||
| + | * mai **eficient** din punct de vedere al performanței | ||
| + | * folosit în special împreună cu ''IntStream'' | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | OptionalInt max = | ||
| + | numbers.stream() | ||
| + | .mapToInt(x -> x) | ||
| + | .max(); | ||
| + | |||
| + | if (max.isPresent()) { | ||
| + | System.out.println(max.getAsInt()); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ===Când folosim fiecare?=== | ||
| + | |||
| + | Folosește ''Optional<T>'' când: | ||
| + | * lucrezi cu **obiecte** | ||
| + | * rezultatul vine dintr-un Stream<T> | ||
| + | * tipul nu este numeric | ||
| + | |||
| + | Folosește ''OptionalInt'' când: | ||
| + | * lucrezi cu **valori numerice primitive** | ||
| + | * folosești ''mapToInt'' | ||
| + | * vrei cod **mai eficient** și **mai clar** | ||
| =====[Optional] Parallel Streams===== | =====[Optional] Parallel Streams===== | ||
| Line 1126: | Line 1460: | ||
| * [[https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html|Stream API - Oracle Docs]] | * [[https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html|Stream API - Oracle Docs]] | ||
| * [[https://www.baeldung.com/java-functional-programming|Java Functional Programming - Baeldung]] | * [[https://www.baeldung.com/java-functional-programming|Java Functional Programming - Baeldung]] | ||
| + | * [[https://www.baeldung.com/java-optional|Guide to Java Optional - Baeldung]] | ||