Introducere în expresiile lambda
Printre noutățile aduse în limbajul Java odată cu lansarea JDK 8, expresiile lambda ocupă un loc special. O lambda reprezintă un set de instrucțiuni care pot fi atribuite unei variabile și apoi apelate de mai multe ori în diverse locuri din program.
Baza unei expresii lambda este operatorul lambda, reprezentat prin săgeata ->. Acest operator împarte expresia lambda în două părți: partea stângă conține lista de parametri ai expresiei, iar partea dreaptă reprezintă corpul expresiei lambda, unde se efectuează acțiunile.
Expresia lambda nu se execută de sine stătător, ci implementează un metodă definită într-o interfață funcțională. Este important ca interfața funcțională să conțină un singur metodă abstractă fără implementare.
Exemplu:
public class LambdaApp {
public static void main(String[] args) {
Operationable operation;
operation = (x, y) -> x + y;
int result = operation.calculate(10, 20);
System.out.println(result); // 30
}
}
interface Operationable {
int calculate(int x, int y);
}
În acest caz, interfața funcțională este Operationable, care definește o singură metodă abstractă - calculate(). Această metodă primește doi parametri de tip întreg și returnează un număr întreg.
Expresiile lambda sunt o formă simplificată a claselor anonime interne, care erau folosite anterior în Java. De exemplu, codul de mai sus poate fi rescris astfel:
public class LambdaApp {
public static void main(String[] args) {
Operationable op = new Operationable() {
public int calculate(int x, int y) {
return x + y;
}
};
int z = op.calculate(20, 10);
System.out.println(z); // 30
}
}
interface Operationable {
int calculate(int x, int y);
}
Pași pentru a folosi o expresie lambda:
1. Definirea unei referințe către o interfață funcțională:
Operationable operation;
2. Crearea unei expresii lambda:
operation = (x, y) -> x + y;
Parametrii expresiei lambda corespund parametrilor metodei unice din interfața funcțională, iar rezultatul corespunde tipului de returnare al metodei. Nu este necesară utilizarea cuvântului cheie return pentru a returna un rezultat dintr-o expresie lambda.
3. Utilizarea expresiei lambda prin apelarea metodei din interfață:
int result = operation.calculate(10, 20);
Deoarece în expresia lambda este definită o operație de adunare a parametrilor, rezultatul metodei va fi suma numerelor 10 și 20.
Deoarece în expresia lambda este definită o operație de adunare a parametrilor, rezultatul metodei va fi suma numerelor 10 și 20.
Pentru aceeași interfață funcțională, putem defini mai multe expresii lambda:
Operationable operation1 = (int x, int y) -> x + y;
Operationable operation2 = (int x, int y) -> x - y;
Operationable operation3 = (int x, int y) -> x * y;
System.out.println(operation1.calculate(20, 10)); // 30
System.out.println(operation2.calculate(20, 10)); // 10
System.out.println(operation3.calculate(20, 10)); // 200
Execuție amânată
Unul dintre aspectele cheie ale utilizării lambdelor este execuția amânată (deferred execution). Astfel, putem defini o expresie lambda într-un loc din program și să o apelăm de câte ori este necesar, în diferite părți ale programului. Execuția amânată poate fi necesară în cazuri precum:
- Executarea codului într-un fir de execuție separat
- Repetarea aceleiași acțiuni de mai multe ori
- Executarea codului în urma unui eveniment
- Executarea codului doar atunci când este necesar
Transmiterea parametrilor către expresia lambda
Parametrii dintr-o expresie lambda trebuie să corespundă tipului parametrilor din metoda interfeței funcționale. Nu este necesar să specificăm tipul parametrilor în expresia lambda, deși putem face acest lucru:
operation = (int x, int y) -> x + y;
Dacă metoda nu primește niciun parametru, se folosesc paranteze goale:
() -> 30 + 20;
Dacă metoda primește doar un singur parametru, putem omite parantezele:
n -> n * n;
Lambde terminale
Lambdele pot să nu returneze niciun rezultat. De exemplu:
interface Printable {
void print(String s);
}
public class LambdaApp {
public static void main(String[] args) {
Printable printer = s -> System.out.println(s);
printer.print("Hello Java!");
}
}
Lambde și variabile locale
O expresie lambda poate folosi variabile definite într-o arie de vizibilitate mai largă – la nivelul clasei sau al metodei în care este definită lambda. Însă, în funcție de locul unde sunt definite variabilele, modul lor de utilizare în lambde poate varia.
Variabile la nivel de clasă:
public class LambdaApp {
static int x = 10;
static int y = 20;
public static void main(String[] args) {
Operation op = () -> {
x = 30;
return x + y;
};
System.out.println(op.calculate()); // 50
System.out.println(x); // 30
}
}
interface Operation {
int calculate();
}
Variabilele x și y sunt declarate la nivel de clasă și pot fi modificate în expresia lambda.
Variabile locale la nivel de metodă:
public static void main(String[] args) {
int n = 70;
int m = 30;
Operation op = () -> {
// n = 100; - nu este permis
return m + n;
};
// n = 100; - de asemenea, nu este permis
System.out.println(op.calculate()); // 100
}
Variabilele locale la nivelul metodei pot fi utilizate și în expresiile lambda, dar nu pot fi modificate. Dacă încercăm să facem acest lucru, mediul de dezvoltare (Intellij IDEA) poate afișa o eroare și indica faptul că variabila respectivă trebuie marcată cu ajutorul cuvântului cheie final, adică trebuie făcută constantă: final int n=70;.
Totuși, acest lucru nu este obligatoriu.
Mai mult, nu vom putea modifica valoarea variabilei care este utilizată într-o expresie lambda în afara acesteia. Chiar dacă variabila respectivă nu este declarată ca și constantă, ea devine, în esență, constantă.
Blocuri de cod în expresiile lambda
Există două tipuri de expresii lambda: expresii pe o singură linie și blocuri de cod. Exemple de expresii pe o singură linie au fost prezentate mai sus. Blocurile de cod sunt delimitate de acolade.
În expresiile lambda care conțin blocuri de cod, putem folosi blocuri interne, bucle, structuri if, switch, putem crea variabile etc. Dacă un bloc lambda trebuie să returneze o valoare, se folosește explicit operatorul return:
Operationable operation = (int x, int y) -> {
if (y == 0)
return 0;
else
return x / y;
};
System.out.println(operation.calculate(20, 10)); // 2
System.out.println(operation.calculate(20, 0)); // 0
Interfața funcțională generică
O interfață funcțională poate fi generică, însă în expresia lambda nu se permite folosirea generics-urilor. În acest caz, trebuie să tipizăm obiectul interfeței cu un anumit tip, care va fi utilizat apoi în expresia lambda. De exemplu:
public class LambdaApp {
public static void main(String[] args) {
Operationable<Integer> operation1 = (x, y) -> x + y;
Operationable<String> operation2 = (x, y) -> x + y;
System.out.println(operation1.calculate(20, 10)); // 30
System.out.println(operation2.calculate("20", "10")); // 2010
}
}
interface Operationable<T> {
T calculate(T x, T y);
}
Astfel, la declararea expresiei lambda, deja se știe ce tip de parametri vor fi utilizați și ce tip va fi returnat.