CodeGym /Corsi /JAVA 25 SELF /Introduzione alle espressioni lambda

Introduzione alle espressioni lambda

JAVA 25 SELF
Livello 21, Lezione 0
Disponibile

1. Introduzione

In breve: un’espressione lambda — è un modo per creare rapidamente l’implementazione di un’interfaccia funzionale “al volo”, senza dichiarare una classe separata o una classe anonima. È come un mini-metodo senza nome che si può passare come argomento o salvare in una variabile.

Per molto tempo Java è stato un linguaggio OOP “classico”. Se volevi passare un pezzetto di comportamento, per esempio cosa fare alla pressione di un pulsante, bisognava scrivere classi anonime:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Pulsante premuto!");
    }
});

Con Java 8 è arrivata la magia delle lambda:

button.setOnClickListener(() -> System.out.println("Pulsante premuto!"));

Dove () -> System.out.println("Pulsante premuto!") — questa è un’espressione lambda.

Formalmente: un’espressione lambda è una forma compatta per scrivere l’implementazione dell’unico metodo astratto di un’interfaccia funzionale.

Perché è importante?

  • Passare il comportamento come valore (per esempio, cosa fare con ogni elemento di una lista).
  • Scrivere codice compatto e leggibile.
  • Usare le API moderne di Java: Stream API, gestione degli eventi, attività asincrone e molto altro.

2. Sintassi delle espressioni lambda

Forma generale

(parametri) -> espressione
// oppure
(parametri) -> { blocco di codice }

Esempi per diverse interfacce

1. Senza parametri (per esempio, Runnable):

Runnable r = () -> System.out.println("Ciao, lambda!");
r.run();

2. Un parametro (per esempio, Consumer):

Consumer<String> printer = s -> System.out.println("Hai passato: " + s);
printer.accept("Java");

Se c’è un solo parametro e il suo tipo può essere dedotto, si possono omettere le parentesi.
Il tipo String è il tipo della variabile (s) che passiamo lì.

3. Più parametri (per esempio, Comparator):

Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5

Il tipo Integer è il tipo delle variabili (a, b) che passiamo lì.

4. Corpo su più righe (servono le parentesi graffe e return, se c’è un ritorno esplicito):

Function<Integer, Integer> square = x -> {
    int result = x * x;
    return result;
};
System.out.println(square.apply(6)); // 36

Il primo Integer è il tipo del risultato della funzione, il secondo Integer è il tipo del parametro.

Abbreviazioni e concisione

  • Se il corpo è una sola espressione, si possono omettere le parentesi e return.
  • Se non ci sono parametri — si scrivono le parentesi vuote: () -> ...
  • Se c’è un solo parametro — si possono omettere le parentesi: x -> ...
  • Se ci sono più parametri — servono le parentesi: (a, b) -> ...

Tabella riassuntiva

Vediamo come la stessa idea si scrive con una lambda e con una classe anonima:

Interfaccia Espressione lambda (esempio) Equivalente di classe anonima
Runnable
() -> System.out.println("Hi")
new Runnable() { public void run() { System.out.println("Hi"); } }
Consumer<String>
s -> System.out.println(s)
new Consumer<String>() { public void accept(String s) { System.out.println(s); } }
Comparator<Integer>
(a, b) -> a - b
new Comparator<Integer>() { public int compare(Integer a, Integer b) { return a - b; } }

Esempio con un’interfaccia personalizzata

Supponiamo di avere un’interfaccia funzionale:

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

Prima si implementava così:

Operation sum = new Operation() {
    @Override
    public void apply(int a, int b) {
        return a + b;
    }
};

Ora — in modo moderno:

Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8

Esempi per le interfacce standard

Runnable:

Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();

Comparator:

List<String> list = Arrays.asList("mela", "banana", "kiwi");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);

Function:

Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123

3. Ambito e cattura delle variabili

Variabili dal contesto esterno (effectively final)

Le espressioni lambda possono usare variabili dal metodo circostante. Ma c’è una regola: tali variabili devono essere effectively final — cioè o dichiarate esplicitamente come final, oppure semplicemente non modificate dopo l’inizializzazione.

Esempio:

String prefix = "Risultato: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix qui è "congelato" - dopo di ciò non si può modificare
System.out.println(f.apply(5)); // Risultato: 10

Se provi a modificare prefix dopo che è stato usato nella lambda, il compilatore segnalerà un errore.

Perché?
Un’espressione lambda può essere invocata dopo l’uscita dal metodo in cui è stata dichiarata la variabile. Per evitare bug “magici”, Java consente di usare solo variabili immutabili.

Differenza rispetto alle classi anonime

Nelle classi anonime vale la stessa regola: le variabili dal metodo esterno devono essere final/effectively final. Ma ci sono differenze di ambito: dentro una classe anonima this si riferisce alla classe anonima stessa, mentre nella lambda — alla classe esterna.

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // Stamperà: Demo$1 (classe anonima)
            }
        };

        Runnable r2 = () -> System.out.println(this); // Stamperà: Demo (classe esterna)

        r1.run();
        r2.run();
    }
}

Lambda e campi della classe

Un’espressione lambda può accedere liberamente ai campi della classe esterna:

public class Counter {
    private int base = 10;

    public void printSum(int x) {
        Function<Integer, Integer> sum = y -> base + y + x;
        System.out.println(sum.apply(5));
    }
}

Qui base — si può modificare (è un campo della classe).
x — deve essere effectively final.

4. Pratica: scriviamo alcune espressioni lambda

Esempio: filtrare una lista di numeri

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("Pari: " + n));

Scoprirai di più su Stream API al livello 30 :P

Esempio: funzione di trasformazione di una stringa

Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA

Esempio: una propria interfaccia funzionale

@FunctionalInterface
interface StringTransformer {
    String transform(String s);
}

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Ciao")); // Ciao!

Esempio: uso di variabili del contesto esterno

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// factor non può essere modificato dopo questo!

5. Errori tipici nell'uso delle espressioni lambda

Errore n. 1: tentativo di modificare una variabile catturata dalla lambda.
Questo codice non verrà compilato — la variabile deve essere effectively final:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Errore: sum non è final!

Se devi accumulare valori, usa un array o un oggetto wrapper.

Errore n. 2: confusione con l’ambito di this.
All’interno di un’espressione lambda this si riferisce alla classe esterna, non alla lambda (a differenza di una classe anonima).

Errore n. 3: dimenticate le parentesi graffe e return in una lambda su più righe.
Se il corpo della lambda non è una sola espressione, servono le parentesi graffe e return:

Function<Integer, Integer> square = x -> {
    int y = x * x;
    return y;
};

Errore n. 4: definizione errata del tipo dell’espressione lambda.
Un’espressione lambda implementa sempre un’interfaccia funzionale. Non puoi semplicemente scrivere:

var f = x -> x + 1; // Errore! Non si sa quale tipo di interfaccia.

Bisogna specificare esplicitamente il tipo:

Function<Integer, Integer> f = x -> x + 1;
1
Compito
JAVA 25 SELF, livello 21, lezione 0
Bloccato
Annuncio vocale del sistema 📣
Annuncio vocale del sistema 📣
1
Compito
JAVA 25 SELF, livello 21, lezione 0
Bloccato
Standardizzazione dell'input utente 📝
Standardizzazione dell'input utente 📝
1
Compito
JAVA 25 SELF, livello 21, lezione 0
Bloccato
Classifica degli atleti per "forza del colpo" 🥊
Classifica degli atleti per "forza del colpo" 🥊
1
Compito
JAVA 25 SELF, livello 21, lezione 0
Bloccato
Generazione dinamica dei report 📊
Generazione dinamica dei report 📊
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION