Sunteți pe pagina 1din 14

Programare

multiparadigmă
JAVA
conf. dr. Cristian IONIȚĂ
cristian.ionita@ase.ro
Interfețe – Metode default și static
Metodele default în cadrul unei interfețe:
◦ Sunt precedate de modificatorul default
◦ Furnizează obligatoriu o implementare pe baza metodelor existente în interfață
◦ Pot fi suprascrise de către clasele care implementează interfața (dar nu este obligatoriu)
◦ Sunt utilizate în special pentru extinderea interfețelor existente

Metodele static în cadrul unei interfețe:


◦ Sunt precedate de modificatorul static
◦ furnizează obligatoriu o implementare pe baza metodelor statice existente
◦ Se accesează folosind NumeInterfata.NumeMetodaStatica
◦ Nu pot fi suprascrise în clasele care implementează interfața
Interfețe funcționale și expresii lambda
O interfață funcțională este o interfață care conține:
◦ Exact o metodă abstractă
◦ Zero sau mai multe metode default
◦ Zero sau mai multe metode statice

Interfețele funcționale pot fi implementate folosind expresii lambda (similar claselor anonime):
(args1, args2, …) -> { body }
◦ Valoarea unei expresii lambda este o referință la o clasă anonimă care implementează o interfață funcțională
◦ Interfața funcțională implementată este stabilită prin context
◦ Semnătura din expresia lambda trebuie să corespundă semnăturii unicei metode abstracte din interfață
◦ Dacă body constă într-o singură instrucțiune de forma { return expresie; } atunci secțiunea poate fi înlocuită
cu expresie ( (a, b) -> { return a + b; } este echivalent cu (a,b) -> a + b)
◦ Dacă există un singur parametru, atunci parantezele rotunde pot lipsi
Expresii lambda
Accesarea variabilelor locale se face în aceleași condiții ca la clasele locale sau anonime: variabilele
locale utilizate în expresia lambda trebuie să fie finale sau efectiv finale (nu se modifică după inițializare)
Variabilele locale nu pot fi mascate in expresiile lambda - expresiile lambda nu creează un nou nivel de
domeniu de vizibilitate
Referința this se referă la clasa care conține expresia și nu la expresie in sine

O referință la o interfață funcțională poate fi inițializată prin intermediul unei referințe la o metodă
existentă într-o clasă în forma NumeClasă::NumeMetoda, numeObiect::NumeMetodă sau
NumeClasă::new pentru constructor.

Exemplu: Program01_InterfeteFunctionaleLambda.java
Interfețe funcționale standard
Function<T,R> - funcție apply care primește un parametru de tip T și întoarce o valoare de tip R
Predicate<T> - funcție test care primește un parametru de tip T și întoarce o valoare booleană
UnaryOperator<T> - funcție apply care primește un parametru de tip T și întoarce o valoare de
tip T
BinaryOperator<T> - funcție apply care primește doi parametri de tip T și întoarce o valoare de
tip T
Consumer<T> - funcție accept care primește un parametru de tip T și consumă valoarea fără a
întoarce un rezultat
Supplier<T> - funcție get care nu primește un parametri și generează un rezultat de tip T
Compunere interfețe funcționale
Operația de compunere presupune combinarea mai multor funcții într-una singură care utilizează intern
funcțiile componente
Compunerea funcțiilor (Function<T,R>)
◦ h = f.compose(g): funcția h va calcula valoarea finală aplicând întâi funcția g asupra parametrului, după care funcția f
pe rezultatul întors de g;
◦ h = f.andThen(g): similar cu compose, dar ordinea de aplicare va fi inversată (întâi f, după care g)

Compunerea predicatelor (Predicate<T>)


◦ h = f.or(g): funcția h va aplica funcțiile f și g pe valoarea primită și va întoarce rezultatele combinate prin intermediul
operatorului logic OR
◦ h = f.and(g): funcția h va aplica funcțiile f și g pe valoarea primită și va întoarce rezultatele combinate prin intermediul
operatorului logic OR

Exemplu: Program02_InterfeteStandard.java
Java Stream API
Definit în pachetul java.util.stream – permite manipularea în stil funcțional pentru colecții
Un stream este un obiect care poate parcurge și procesa, secvențial sau paralel, o colecție și
implementează interfața BaseStream<T,S extends BaseStream<T,S>>
Caracteristici:
◦ Nu este o structură de date; dale propriu-zise se află într-un vector, colecție, canal IO, ...
◦ Nu modifică structura de date (nu pot adăuga / șterge elemente)
◦ Operațiile specificate se execută doar la finalul procesării (la întâlnirea unei operații terminale)

Utilizarea presupune
◦ Construirea unui obiect stream (în general pe baza unei colecții)
◦ Aplicarea unui lanț de operații intermediare
◦ Aplicarea unei operații terminale pentru execuția efectivă și obținerea rezultatului

Operațiile pe obiecte stream se pot executa concurent folosind operația intermediară parallel()
Construire obiect Stream
Metode de obținere:
◦ Metoda stream() disponibilă în clasele colecție
List<Integer> lista = List.of(1, 2, 3);
Stream<Integer> stream = lista.stream();

◦ Metoda statică stream() din clasa Arrays


Persoana[] vector = new Persoana[]{new Persoana(), new Persoana()};
Stream<Persoana> stream = Arrays.stream(vector);

◦ Metoda lines() din clasa BufferedReader


try (var fisier = new BufferedReader(new FileReader("date.txt"))) {
Stream<String> stream = fisier.lines(); }

◦ Metodele statice of(valori), iterate(start, stop) și generate(supplier) din clasa Stream


Operații intermediare pe obiecte Stream
Operațiile intermediare – au ca rezultat un alt obiect stream și pot fi înlănțuite:
◦ map(Function) – rezultatul aplicării funcției pe elementele colecției originale; rezultatul va avea același
număr de elemente, dar cu tip și / sau valori diferite
◦ filter(Predicate) – un stream cu un subset al elementelor colecției originale
◦ distinct() – un stream cu elementele distincte din colecția originală
◦ limit(int) – întoarce un stream care conține maxim numărul de elemente specificate
◦ sorted(Comparator) – un stream cu elementele originale sortate în funcție de comparatorul furnizat
◦ Se poate utiliza metoda statică Comparator.comparing(keyExtractor):
persoane.stream().sorted(Comparator.comparing(Persoana::getVarsta))

◦ flatMap(Function) – produce un stream care conține concatenarea rezultatului aplicării funcției pe


elementele colecției; funcția trebuie să întoarcă un stream pentru fiecare element;
Stream<Factura> facturi = clienti.stream().flatMap(c -> c.getFacturi().stream());
Operații terminale pe obiecte Stream
Operații terminale – produc rezultatul final și declanșează procesarea:
◦ forEach(Consumer) – aplică funcția furnizată pentru fiecare element din stream
◦ collect (Collector) – furnizează elementele procesate într-o colecție
◦ toArray(tip[]::new) – furnizează elementele procesate într-un vector
◦ count() – returnează numărul de elemente din colecția procesată
◦ anyMatch/allMatch/noneMatch(Predicate) – întorc o valoare booleană
◦ findAny/findFirst() – întoarce un element oarecare / primul element dacă avem o sortare
◦ min/max(Comparator) – întoarce elementul minim / maxim
◦ reduce(T, BinaryOperator<T>) – agregă elementele din stream pe baza funcției asociative furnizate

Metodele findAny/findFirst/min/max întorc un obiect de tip Optional cu diverse


metode utile pentru a trata cazurile în care elementul nu este găsit sau obiectul
stream este vid (isPresent, isEmpty, get, orElse, ...)
Operații de reducere pe obiecte Stream
Componente:
◦ Identity: valoarea inițială pentru operație (și rezultatul final în cazul în care colecția este vidă)
◦ Accumulator: funcția asociativă care preia două valori (rezultatul parțial obținut la pasul anterior și valoarea curentă) și
produce următorul rezultat intermediar
◦ Combine: funcția asociativă care combină două rezultate parțiale (necesară pentru paralelizare) și respectă condițiile:
◦ combiner(identity, u) == u și combiner(u, accumulator(identity, t)) == accumulator(u, t)

În general, metoda reduce lucrează cu valori imutabile.


Exemple pentru variantele uzuale T reduce(T identity, BinaryOperator<T> accumulator) și
U reduce​(U identity, BiFunction<U,​T,​U> accumulator, BinaryOperator<U> combiner):
Stream.of("Ana", "are", "mere").reduce("", (sir, cuvant) -> sir + " " + cuvant)
double total = facturi.stream().reduce(0,
(total, factura) -> total + factura.getValoare(), Double::sum);

Exemplu mai complex: Program03_Streams.java


Operații de colectare pe obiecte Stream
Obținere obiecte Collector care sumarizează elementele dintr-un stream:
◦ Collectors.counting() – obține numărul de elemente din stream;
◦ Collectors.summingDouble/Int/Long(ToXFunction) – produce suma valorilor obținute prin aplicarea
funcției pe obiectele din stream;
◦ Collectors.summarizingDouble/Int/Long(ToXFunction) – produce un obiect de tip
Double/Int/LongSummaryStatistics prin agregarea valorilor obținute prin aplicarea funcției pe obiectele
din stream; obiectul rezultat conține informații despre:
◦ Numărul de elemente
◦ Media și suma elementelor
◦ Valorile minime și maxime
◦ Collectors.joining(String) – construiește un șir de caractere din elementele stream-ului separate prin
delimitatorul specificat
◦ Collectors.maxBy/minBy(Comparator) – obține elementul maxim / minim din stream pe baza
comparatorului furnizat
Operații de colectare pe obiecte Stream
Obținere obiecte Collector care produc colecții pe baza datelor din stream:
◦ Collectors.toList() – construiește o listă pe baza elementelor din stream;
◦ Collectors.toSet() – construiește o mulțime pe baza elementelor din stream;
◦ Collectors.toCollection(Supplier<T>): colectează elementele într-o colecție construită pe baza funcției
furnizate (exemplu: stream.collect(Collectors.toCollection(TreeSet::new));
◦ Collector.toMap(Function<T,K>, Function<T,V>) – construiește un dicționar pe baza elementelor din
stream utilizând funcțiile furnizate pentru a determina cheia și valoarea corespunzătoare fiecărui
element; cheile rezultate trebuie să fie unice.

Obținere obiecte Collector pentru gruparea elementelor din stream într-un dicționar:
◦ Collectors.groupingBy(Function<T,K>): produce un dicționar unde cheile sunt produse pe baza funcției
furnizate și valorile sunt liste cu elementele corespunzătoare;
◦ Collectors.groupingBy(Function<T,K>, Collector): produce un dicționar unde cheile sunt produse pe baza
funcției furnizate și valorile sunt obținute pe baza colectorului furnizat;
Obiecte stream specializate
Există obiecte stream specializate pentru tipurile fundamentale
Se obțin prin apelarea metodelor mapToInt/Long/DoubleStream(funcție)
Conțin diverse metode utile:
◦ sum() – suma elementelor
◦ average() – media elementelor
◦ max/min() – valoarea maximă / minimă
◦ count() – numărul de elemente
◦ toArray() – un vector de tipuri primitive
◦ sorted() – un stream specializat sortat

Pot fi convertite la obiecte stream normale prin metoda boxed()

S-ar putea să vă placă și