Expresii lambda în Java

O lambda este o funcție scurtă, fără nume, scrisă ca parametri -> corp, folosită oriunde se așteaptă o interfață funcțională.

Sintaxă
x -> x * 2
(a, b) -> a + b
() -> "fără parametri"
(String s) -> { System.out.println(s); return s.length(); }

Ce face Expresii lambda

O expresie lambda scrie o bucată de comportament ca pe o valoare: n -> n % 2 == 0 este „funcția care spune dacă un număr e par”. O poți pune într-o variabilă, o poți trimite unei metode (lista.removeIf(...), lista.sort(...)) sau o poți returna.

Tipul unei lambda este o interfață funcțională: o interfață cu exact o metodă abstractă. Pachetul java.util.function are cele mai folosite: Function<T, R> (primește T, dă R), Predicate<T> (dă boolean), Consumer<T> (nu dă nimic), Supplier<T> (nu primește nimic), BiFunction, UnaryOperator. Și interfețele vechi precum Runnable și Comparator sunt funcționale.

Forme: cu un singur parametru, parantezele pot lipsi; tipurile parametrilor se deduc; dacă corpul e o singură expresie, nu ai nevoie de acolade și de return. O lambda poate folosi variabile locale din jur doar dacă sunt efectiv finale (nu se modifică).

Exemple

Exemplul 1: Lambda în metodele colecțiilor

removeIf primește un Predicate, replaceAll un UnaryOperator, sort un Comparator și forEach un Consumer.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> orase = new ArrayList<>(List.of("iași", "cluj-napoca", "arad", "timișoara", "deva"));

        orase.removeIf(o -> o.length() <= 4);
        System.out.println("fără nume scurte: " + orase);

        orase.replaceAll(o -> o.substring(0, 1).toUpperCase() + o.substring(1));
        orase.sort((a, b) -> b.compareTo(a));
        orase.forEach(o -> System.out.println("- " + o));
    }
}

Rezultat

fără nume scurte: [cluj-napoca, timișoara]
- Timișoara
- Cluj-napoca

Exemplul 2: Interfețele din java.util.function

Fiecare variabilă are tipul unei interfețe funcționale și se apelează prin metoda ei: apply, test, accept, get. andThen și negate combină funcții.

import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Function<Double, Double> cuTva = pret -> pret * 1.21;
        Function<Double, Long> rotunjit = x -> Math.round(x);
        Predicate<Integer> promovat = nota -> nota >= 5;
        Consumer<String> salut = nume -> System.out.println("Bună, " + nume + "!");
        Supplier<String> oras = () -> "Iași";
        BiFunction<Integer, Integer, Integer> arie = (l, h) -> l * h;

        System.out.println("100 lei cu TVA: " + cuTva.apply(100.0));
        System.out.println("rotunjit: " + cuTva.andThen(rotunjit).apply(99.9));
        System.out.println("4 promovat? " + promovat.test(4) + ", corigent? " + promovat.negate().test(4));
        salut.accept("Mihaela");
        System.out.println("oraș implicit: " + oras.get());
        System.out.println("arie 5x8: " + arie.apply(5, 8));
    }
}

Rezultat

100 lei cu TVA: 121.0
rotunjit: 121
4 promovat? false, corigent? true
Bună, Mihaela!
oraș implicit: Iași
arie 5x8: 40

Exemplul 3: Lambda cu bloc și variabile capturate

Lambda cu acolade are nevoie de return. Variabila prag este capturată din metodă; pentru că nu se mai schimbă, este efectiv finală. O metodă poate returna o lambda, ca o „fabrică” de funcții.

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

public class Main {
    static Function<Double, Double> reducere(int procent) {
        return pret -> pret * (100 - procent) / 100;
    }

    public static void main(String[] args) {
        double prag = 50;
        Predicate<Double> scump = pret -> pret > prag;

        Function<String, String> eticheta = (String text) -> {
            String curat = text.strip();
            if (curat.isEmpty()) {
                return "(fără nume)";
            }
            return curat.toUpperCase();
        };

        for (double p : List.of(19.9, 75.0, 120.0)) {
            System.out.println(p + " scump? " + scump.test(p) + ", cu -20%: " + reducere(20).apply(p));
        }
        System.out.println(eticheta.apply("  brânză de burduf "));
        System.out.println(eticheta.apply("   "));
    }
}

Rezultat

19.9 scump? false, cu -20%: 15.92
75.0 scump? true, cu -20%: 60.0
120.0 scump? true, cu -20%: 96.0
BRÂNZĂ DE BURDUF
(fără nume)

Greșeli frecvente

  • Modifici o variabilă locală folosită în lambda (total += x în forEach): local variables referenced from a lambda expression must be final or effectively final. Folosește un stream cu sum() sau o buclă obișnuită.
  • Uiți return într-o lambda cu acolade care trebuie să returneze ceva: missing return statement, sau bad return type in lambda expression.
  • Pui o lambda într-o variabilă var sau Object: lambda expression not expected here. Lambda are nevoie de un tip țintă care este o interfață funcțională.

Comenzi înrudite

  • Referințe la metode (::): O referință la metodă (Clasa::metoda) este o lambda prescurtată care doar apelează o metodă existentă.
  • Interfețe (interface, implements): O interfață (interface) descrie ce metode trebuie să aibă o clasă; clasa o promite cu implements și poate implementa mai multe interfețe.
  • Stream-uri (ideea): Un stream descrie o prelucrare de date ca un lanț: sursă, operații intermediare (filter, map, sorted) și o operație finală (toList, count, collect).
  • Comparatori (Comparable și Comparator): Comparable dă ordinea naturală a unei clase (compareTo); Comparator descrie orice altă ordine și se combină cu comparing, thenComparing și reversed.
  • Function: O funcție care primește o valoare de tip T și returnează una de tip R; o apelezi cu apply.
  • Predicate: O condiție: primește o valoare și răspunde true sau false prin metoda test; folosită de filter, removeIf, anyMatch.