1. Espressioni lambda e collezioni
Ripassiamo come appariva l’elaborazione delle collezioni prima dell’arrivo delle espressioni lambda. Supponiamo di avere un elenco di stringhe e di volerle stampare a schermo:
List<String> list = Arrays.asList("gatto", "cane", "riccio");
for (String s : list) {
System.out.println(s);
}
Tutto semplice, ma se vogliamo, per esempio, eliminare dall’elenco tutte le stringhe vuote, dobbiamo scrivere un ciclo con condizione e talvolta usare un iteratore (altrimenti ci sarà una ConcurrentModificationException). Oppure, per esempio, ordinare per lunghezza della stringa:
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Anche per un compito così semplice — già 5 righe di codice e un mucchio di parentesi «rumorose». Come ottimizzare? La risposta la conoscete già: espressioni lambda.
Uso delle espressioni lambda nei metodi delle collezioni
A partire da Java 8 le interfacce delle collezioni hanno ricevuto nuovi metodi che accettano interfacce funzionali — il che significa che possiamo passarvi espressioni lambda. Ecco i più popolari:
- forEach(Consumer<T> action)
- removeIf(Predicate<T> filter)
- sort(Comparator<T> c)
- replaceAll(UnaryOperator<T> operator)
Esempio: forEach
Stampare tutti gli elementi della lista a schermo (modo tradizionale):
for (String s : list) {
System.out.println(s);
}
Ora — con una lambda:
list.forEach(s -> System.out.println(s));
Oppure ancora più corto, se volete giocare a fare il «guru di Java»:
list.forEach(System.out::println); // method reference, lo vedremo più avanti
Esempio: removeIf
Rimuovere tutte le stringhe vuote dalla lista:
List<String> animals = new ArrayList<>(Arrays.asList("gatto", "", "cane", "riccio", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [gatto, cane, riccio]
Esempio: sort
Ordinamento della lista per lunghezza della stringa:
List<String> animals = Arrays.asList("gatto", "cane", "riccio", "elefante");
animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [cane, gatto, riccio, elefante]
Esempio: replaceAll
Convertire tutte le stringhe in maiuscolo:
List<String> animals = new ArrayList<>(Arrays.asList("gatto", "cane", "riccio"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [GATTO, CANE, RICCIO]
2. Stream API ed espressioni lambda
Con l’uscita di Java 8 è apparso lo Stream API — uno strumento potente per l’elaborazione delle collezioni in stile funzionale. Gli stream permettono di filtrare, trasformare, ordinare, raccogliere collezioni tramite catene di metodi. E tutti questi metodi accettano espressioni lambda!
Importante: L’analisi completa dello Stream API arriverà più avanti; per ora — solo esempi di base per capire il ruolo delle lambda.
Esempio: filtraggio
Lasciare solo le stringhe più lunghe di 3 caratteri:
List<String> animals = Arrays.asList("gatto", "elefante", "riccio", "coccodrillo");
animals.stream()
.filter(s -> s.length() > 3)
.forEach(System.out::println); // gatto, elefante, riccio, coccodrillo
Esempio: trasformazione (map)
Rendere tutte le stringhe in maiuscolo:
List<String> animals = Arrays.asList("gatto", "elefante", "riccio");
List<String> upper = animals.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
System.out.println(upper); // [GATTO, ELEFANTE, RICCIO]
Esempio: ordinamento
Ottenere un elenco ordinato per lunghezza (senza modificare l’originale):
List<String> animals = Arrays.asList("gatto", "elefante", "riccio", "coccodrillo");
List<String> sorted = animals.stream()
.sorted((a, b) -> a.length() - b.length())
.collect(Collectors.toList());
System.out.println(sorted); // [gatto, riccio, elefante, coccodrillo]
3. Confronto con le classi anonime
Confrontiamo come apparirebbe lo stesso codice con una classe anonima e con una lambda.
Ordinamento per lunghezza della stringa
Classe anonima:
animals.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Espressione lambda:
animals.sort((a, b) -> a.length() - b.length());
Conclusione:
L’espressione lambda risparmia molte righe e rende il codice più leggibile. Meno parentesi, meno rumore — più sostanza!
Eliminazione delle stringhe vuote
Classe anonima:
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
});
Espressione lambda:
animals.removeIf(s -> s.isEmpty());
4. Pratica: brevi esercizi con espressioni lambda
Mettiamo in pratica le espressioni lambda in una mini‑app per lavorare con un elenco di utenti.
Esempio 1: Filtrare gli utenti per età
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.stream()
.filter(u -> u.age >= 18)
.forEach(System.out::println); // Bob (25)
Esempio 2: Ordinare gli utenti per nome
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alice (17), Bob (25), Charlie (15)]
Esempio 3: Trasformare l’elenco degli utenti in un elenco di nomi
List<String> names = users.stream()
.map(u -> u.name)
.collect(Collectors.toList());
System.out.println(names); // [Alice, Bob, Charlie]
Esempio 4: Rimuovere tutti i minorenni
List<User> users = new ArrayList<>(Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
));
users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]
5. Sfumature utili
Particolarità: ambito e variabili «effettivamente final»
Un’espressione lambda può usare variabili dal metodo esterno, ma solo se sono final o «effettivamente final» (cioè non vengono modificate dopo l’inizializzazione).
int minAge = 18;
users.stream()
.filter(u -> u.age >= minAge)
.forEach(System.out::println);
Se provi a modificare minAge dopo averla usata nella lambda, il compilatore segnalerà un errore.
Tabella: metodi principali di collezioni e stream con espressioni lambda
| Metodo di collezione/stream | Cosa fa | Tipo di espressione lambda | Esempio |
|---|---|---|---|
|
Per ogni elemento | |
|
|
Rimuove gli elementi in base a una condizione | |
|
|
Ordina gli elementi | |
|
|
Sostituisce ogni elemento | |
|
|
Filtra lo stream | |
|
|
Trasforma gli elementi | |
|
|
Itera sullo stream | |
|
|
Ordinamento nello stream | |
|
7. Errori tipici
Errore n. 1: La lambda è troppo lunga. Se dentro un’espressione lambda hai già 5 righe di codice, condizioni, cicli e try-catch, molto probabilmente conviene estrarre quel codice in un metodo separato. Le lambda sono ottime per logiche brevi.
Errore n. 2: Uso di variabili mutabili. Se provi a modificare dentro la lambda una variabile del metodo esterno (per esempio, un contatore), il compilatore non lo permetterà. La variabile deve essere final o «effettivamente final».
Errore n. 3: Dimenticare che i metodi delle collezioni/stream non sempre modificano la collezione originale. Per esempio, stream().filter(...) non modifica la lista originale, ma restituisce un nuovo stream. Se vuoi ottenere una collezione — usa collect(Collectors.toList()).
Errore n. 4: L’espressione lambda non corrisponde al tipo richiesto. Se un metodo accetta, per esempio, un Comparator<T> e provi a passare una lambda con un solo parametro (invece di due), otterrai un errore di compilazione.
Errore n. 5: Perdita di leggibilità con lambda annidate. Se hai una catena di map, filter, forEach e dentro ogni lambda ce n’è un’altra, il codice diventa illeggibile. In questi casi è meglio dividere l’espressione in passaggi separati o estrarre parti in metodi.
GO TO FULL VERSION