Lambda-uri ca parametri și rezultate ale metodelor
Unul dintre avantajele lambda-urilor în Java este că ele pot fi transmise ca parametri în metode. Să analizăm un exemplu:
public class LambdaApp {
public static void main(String[] args) {
Expression func = (n) -> n % 2 == 0;
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(sum(nums, func)); // 20
}
private static int sum (int[] numbers, Expression func) {
int result = 0;
for(int i : numbers) {
if (func.isEqual(i))
result += i;
}
return result;
}
}
interface Expression {
boolean isEqual(int n);
}
Interfața funcțională Expression definește metoda isEqual(), care returnează true dacă pentru numărul n este îndeplinită o anumită condiție.
În clasa principală a programului este definită metoda sum(), care calculează suma tuturor elementelor dintr-un array care îndeplinesc o anumită condiție. Condiția este transmisă prin parametrul Expression func. În momentul scrierii metodei sum, nu știm exact ce condiție va fi utilizată. Condiția este definită sub formă de expresie lambda:
Expression func = (n) -> n % 2 == 0;
Astfel, în acest caz, toate numerele trebuie să fie pare, adică restul împărțirii lor la 2 trebuie să fie 0. Apoi, această expresie lambda este transmisă în apelul metodei sum.
Se poate de asemenea să nu definim o variabilă pentru interfață și să transmitem direct expresia lambda în metodă:
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int x = sum(nums, (n) -> n > 5); // suma numerelor mai mari de 5
System.out.println(x); // 30
Referințe la metode ca parametri ai metodelor
Începând cu JDK 8, în Java putem transmite o referință la o metodă ca parametru într-o altă metodă. Acest mod este similar cu transmiterea unei expresii lambda în metodă.
O referință la metodă este transmisă în formatul numele_clasei::numele_metodei_statice (dacă metoda este statică) sau obiect_clasa::numele_metodei (dacă metoda nu este statică). Să analizăm un exemplu:
interface Expression {
boolean isEqual(int n);
}
class ExpressionHelper {
static boolean isEven(int n) {
return n % 2 == 0;
}
static boolean isPositive(int n) {
return n > 0;
}
}
public class LambdaApp {
public static void main(String[] args) {
int[] nums = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};
System.out.println(sum(nums, ExpressionHelper::isEven));
Expression expr = ExpressionHelper::isPositive;
System.out.println(sum(nums, expr));
}
private static int sum(int[] numbers, Expression func) {
int result = 0;
for (int i : numbers) {
if (func.isEqual(i))
result += i;
}
return result;
}
}
În acest caz, interfața funcțională Expression are o singură metodă. În plus, este definită clasa ExpressionHelper, care conține două metode statice. Aceste metode puteau fi definite și în clasa principală a programului, dar au fost plasate într-o clasă separată.
În clasa principală LambdaApp este definită metoda sum(), care returnează suma elementelor dintr-un array ce îndeplinesc o anumită condiție. Condiția este transmisă ca un obiect al interfeței funcționale Expression.
În metoda main, apelăm de două ori metoda sum, transmițând același array de numere, dar condiții diferite. În primul apel:
System.out.println(sum(nums, ExpressionHelper::isEven));
La locul celui de-al doilea parametru este transmisă referința la metoda statică isEven() din clasa ExpressionHelper. Metodele la care se face referință trebuie să aibă aceleași parametri și rezultate ca metoda interfeței funcționale.
În cel de-al doilea apel al metodei sum, creăm separat un obiect Expression, care este apoi transmis în metodă:
Expression expr = ExpressionHelper::isPositive;
System.out.println(sum(nums, expr));
Utilizarea referințelor la metode ca parametri este similară cu utilizarea expresiilor lambda.
Dacă trebuie să apelăm metode non-statice, în referință, în loc de numele clasei, utilizăm numele obiectului acestei clase:
interface Expression {
boolean isEqual(int n);
}
class ExpressionHelper {
boolean isEven(int n) {
return n % 2 == 0;
}
}
public class LambdaApp {
public static void main(String[] args) {
int[] nums = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};
ExpressionHelper exprHelper = new ExpressionHelper();
System.out.println(sum(nums, exprHelper::isEven)); // 0
}
private static int sum(int[] numbers, Expression func) {
int result = 0;
for (int i : numbers) {
if (func.isEqual(i))
result += i;
}
return result;
}
}
Referințe la constructori
În mod similar, putem folosi referințele la constructori: numele_clasei::new. De exemplu:
public class LambdaApp {
public static void main(String[] args) {
UserBuilder userBuilder = User::new;
User user = userBuilder.create("Tom");
System.out.println(user.getName());
}
}
interface UserBuilder {
User create(String name);
}
class User {
private String name;
String getName() {
return name;
}
User(String n) {
this.name = n;
}
}
Când folosim constructori, metodele interfețelor funcționale trebuie să accepte aceleași liste de parametri ca și constructorii clasei și să returneze un obiect al acestei clase.
Lambda-uri ca rezultat al metodelor
De asemenea, o metodă din Java poate returna o expresie lambda. Iată un exemplu:
interface Operation {
int execute(int x, int y);
}
public class LambdaApp {
public static void main(String[] args) {
Operation func = action(1);
int a = func.execute(6, 5);
System.out.println(a); // 11
int b = action(2).execute(8, 2);
System.out.println(b); // 6
}
private static Operation action(int number) {
switch (number) {
case 1: return (x, y) -> x + y;
case 2: return (x, y) -> x - y;
case 3: return (x, y) -> x * y;
default: return (x, y) -> 0;
}
}
}
În acest caz, este definită o interfață funcțională Operation, în care metoda execute acceptă două valori de tip int și returnează o valoare de tip int.
Metoda action primește ca parametru un număr și, în funcție de valoarea sa, returnează o expresie lambda corespunzătoare pentru adunare, scădere, înmulțire sau returnează 0.
În metoda main, putem apela această metodă action. Mai întâi, obținem rezultatul său – o expresie lambda, care este atribuită variabilei Operation. Apoi, folosim metoda execute pentru a executa această expresie lambda:
Operation func = action(1);
int a = func.execute(6, 5);
System.out.println(a); // 11
Sau putem obține și executa expresia lambda imediat:
int b = action(2).execute(8, 2);
System.out.println(b); // 6