CodeGym /Corsi /JAVA 25 SELF /Espressioni lambda: sintassi, ambiti di visibilità

Espressioni lambda: sintassi, ambiti di visibilità

JAVA 25 SELF
Livello 48, Lezione 0
Disponibile

1. Introduzione

Diciamolo chiaramente: scrivere classi anonime per una o due righe di codice — è come noleggiare un enorme camion per trasportare dal panificio al negozio un solo panino.

Per esempio, se volete ordinare un elenco di stringhe per lunghezza, prima di Java 8 bisognava scrivere così:

List<String> list = Arrays.asList("mela", "banana", "kiwi");
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Il compito sembra semplice, ma il codice occupa mezza schermata. È particolarmente fastidioso quando di operazioni simili ce ne sono tante: il codice diventa “rumoroso” e si perde l’essenza del problema. I programmatori hanno pianto, sofferto e poi hanno inventato le espressioni lambda. Le abbiamo già studiate un po’, ora ripasseremo e approfondiremo le nostre conoscenze.

Che cos’è un’espressione lambda

Un’espressione lambda è una forma compatta per scrivere l’implementazione di un’interfaccia funzionale, cioè un’interfaccia con un unico metodo astratto (ad esempio, Comparator, Runnable, Consumer e molte altre).

In parole povere, un’espressione lambda vi permette di scrivere una funzione “al volo”, proprio dove serve, senza dichiarare una classe o un metodo separato.

Sintassi generale:

(parametri) -> { corpo }

Esempi:

  • (a, b) -> a + b — funzione che somma due numeri
  • x -> x * x — funzione che eleva un numero al quadrato
  • () -> System.out.println("Hello!") — funzione senza parametri

Relazione con le interfacce funzionali:
Un’espressione lambda può sempre essere assegnata a una variabile di tipo interfaccia funzionale o passata come argomento a un metodo che si aspetta tale interfaccia.

2. Sintassi delle espressioni lambda

Senza parametri

Runnable r = () -> System.out.println("Ciao, mondo!");
r.run(); // Stamperà: Ciao, mondo!

Un parametro
Se il parametro è uno solo, le parentesi si possono omettere:

Consumer<String> print = s -> System.out.println(s);
print.accept("Java è fantastica!");

Più parametri
Le parentesi sono obbligatorie:

Comparator<String> cmp = (a, b) -> a.length() - b.length();

Corpo con una sola espressione
Se il corpo è composto da una sola espressione, non servono le parentesi graffe né return:

Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

Corpo a blocco
Se servono più istruzioni, usate le parentesi graffe e return (se c’è un valore di ritorno):

Function<Integer, Integer> abs = x -> {
    if (x < 0) {
        return -x;
    }
    return x;
};
System.out.println(abs.apply(-3)); // 3

Tipi dei parametri
Spesso i tipi dei parametri non è necessario specificarli — il compilatore li deduce dal contesto. Ma se volete, potete indicarli esplicitamente:

Comparator<String> cmp = (String a, String b) -> a.length() - b.length();

Lambda senza valore di ritorno
Se l’interfaccia restituisce void, scrivete semplicemente le istruzioni:

list.forEach(s -> System.out.println("Elemento: " + s));

Tabella: Varianti di sintassi delle espressioni lambda

Cosa facciamo Esempio Commento
Senza parametri
() -> System.out.println("Hi")
Per esempio, per Runnable
Un parametro
x -> x * x
Si può senza parentesi
Più parametri
(a, b) -> a + b
Parentesi obbligatorie
Un’espressione
x -> x + 1
Senza return e senza parentesi graffe
Blocco di codice
x -> { int y = x + 1; return y * 2; }
Con return, se c’è un risultato

3. Applicazioni: dove e come usare le espressioni lambda

Le espressioni lambda si usano soprattutto dove serve passare un “comportamento” — una funzione — come argomento. È stata una vera rivoluzione per collezioni, stream (Stream API), eventi e molto altro.

Ordinamento di una lista

Prima di Java 8:

list.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Con un’espressione lambda:

list.sort((a, b) -> a.length() - b.length());

Creare un thread (Thread)

Thread t = new Thread(() -> System.out.println("Thread avviato!"));
t.start();

Elaborazione di collezioni

list.forEach(s -> System.out.println(s.toUpperCase()));

Filtrare una lista

List<String> longWords = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());

Esempio: sviluppiamo la nostra applicazione didattica

Supponiamo di avere un elenco di utenti:

List<String> users = Arrays.asList("Alice", "Bob", "Charlie");

Stampare tutti gli utenti i cui nomi sono più lunghi di 4 caratteri:

users.stream()
    .filter(name -> name.length() > 4)
    .forEach(name -> System.out.println("Utente: " + name));

4. Ambito di visibilità delle variabili nelle espressioni lambda

Le espressioni lambda possono usare variabili dal metodo circostante, ma ci sono delle sfumature!

Variabili e lambda

Una lambda in Java può “catturare” solo variabili che, dopo l’inizializzazione, non cambiano più. Se la variabile è dichiarata con final — tutto chiaro. Ma anche se la parola final non è scritta, il compilatore verifica da sé se il valore cambia o no. Se non cambia, la considera «come se fosse final» e la ammette tranquillamente nella lambda.

Esempio:

int minLength = 4; // il valore non cambia da nessuna parte
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});

Qui funziona tutto, perché minLength rimane lo stesso numero.

Ma se dopo l’uso nella lambda provate a riassegnare minLength, otterrete un errore di compilazione:

int minLength = 4;
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});
minLength = 10; // Errore! La lambda ha già "catturato" il valore

In sostanza la regola è molto semplice: la variabile che entra nella lambda deve essere immutabile.

Differenza rispetto alle classi anonime

Nelle classi anonime e nelle espressioni lambda le variabili del metodo esterno funzionano allo stesso modo: solo final/effettivamente final.

MA!
In un’espressione lambda this si riferisce all’oggetto esterno (l’istanza corrente della classe), mentre in una classe anonima — all’istanza della classe anonima. È importante se dentro la lambda accedete ai campi o ai metodi della classe corrente.

Esempio:

public class Example {
    String name = "Classe esterna";

    void demo() {
        Runnable r1 = new Runnable() {
            String name = "Classe anonima";
            @Override
            public void run() {
                System.out.println(this.name); // "Classe anonima"
            }
        };

        Runnable r2 = () -> System.out.println(this.name); // "Classe esterna"

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

5. Errori tipici nel lavoro con le espressioni lambda

Errore n. 1: uso di una variabile non final/non effettivamente final. Se decidete di modificare una variabile dopo averla usata nella lambda, il compilatore vi fermerà subito. È per sicurezza: altrimenti non sarebbe chiaro quale valore della variabile usare.

Errore n. 2: confusione con this. In un’espressione lambda this è la classe esterna, mentre in una classe anonima è la classe anonima stessa. Se cercate di chiamare un metodo della classe esterna da una lambda, tutto funzionerà; da una classe anonima — no (se contavate sul contesto della classe esterna).

Errore n. 3: lambda senza contesto. Un’espressione lambda non può essere usata da sola — va assegnata a una variabile di un’interfaccia funzionale oppure passata dove tale interfaccia è attesa. Il tentativo di scrivere semplicemente x -> x + 1 fuori contesto causerà un errore.

Errore n. 4: lambda troppo complessa. Se un’espressione lambda supera le 3–5 righe, diventa difficile da leggere. In questi casi è meglio estrarre la logica in un metodo separato.

1
Compito
JAVA 25 SELF, livello 48, lezione 0
Bloccato
Laboratorio di ordinamento dei nomi 📜
Laboratorio di ordinamento dei nomi 📜
1
Compito
JAVA 25 SELF, livello 48, lezione 0
Bloccato
Classificatore mistico dei numeri 🔮
Classificatore mistico dei numeri 🔮
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION