4. Espressioni lambda in Java 8
4.1. Esempio-01 - Interfacce funzionali e lambda
![]() |
Si consideri il seguente codice:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// anonymous classes
I1 ia1 = new I1() {
@Override
public void doSomething() {
System.out.println("ia1.doSomething");
}
};
I2 ia2 = new I2() {
@Override
public String getSomething(double value) {
return String.format("ia2.getSomething(%s)", value);
}
};
// lambdas
I1 ib1 = () -> System.out.println("ib1.lambda");
I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
I1 ib3 = () -> {
System.out.println("ib3.lambda");
};
I2 ib4 = (double value) -> {
return String.format("ib4.lambda(%s)", value);
};
// app
ia1.doSomething();
System.out.println(ia2.getSomething(4.3));
ib1.doSomething();
System.out.println(ib2.getSomething(5.8));
ib3.doSomething();
System.out.println(ib4.getSomething(10.1));
}
}
@FunctionalInterface
interface I1 {
void doSomething();
}
@FunctionalInterface
interface I2 {
String getSomething(double value);
}
- righe 41–44: definiscono un'interfaccia funzionale I1. Un'interfaccia funzionale è un'interfaccia con un solo metodo. Non dipende dalla presenza dell'annotazione [@FunctionalInterface] alla riga 41, che è facoltativa;
- righe 46–49: una seconda interfaccia funzionale I2;
- righe 6–19: le interfacce I1 e I2 sono implementate utilizzando classi anonime, la soluzione più comune prima dell'introduzione delle funzioni lambda;
- righe 21–29: le interfacce I1 e I2 sono implementate utilizzando funzioni lambda;
- righe 7–12: implementazione dell'interfaccia I1 con una classe anonima. La sintassi per implementare un'interfaccia I con una classe anonima è la seguente:
dove m1, m2, ... sono i metodi definiti dall'interfaccia I.
- righe 14–19: implementazione dell'interfaccia I2 con una classe anonima;
- riga 22: implementazione dell'interfaccia I1 con una funzione lambda. Qui, sfruttiamo il fatto che l'interfaccia funzionale ha un solo metodo. La funzione lambda implementa quindi questo unico metodo M. La sua sintassi è la seguente:
I tipi T1, T2, Tn possono essere omessi se il compilatore è in grado di dedurli dal contesto (inferenza di tipo).
- riga 22: implementazione del metodo [I1.doSomething] con firma:
void doSomething();
[doSomething] è un metodo che non ha parametri e non restituisce alcun risultato. La sua implementazione lambda può essere scritta come nella riga 22 o come nelle righe 24–26, ovvero è possibile racchiudere il codice della funzione lambda tra parentesi graffe. Se questo codice contiene una sola istruzione, come in questo caso, le parentesi graffe possono essere omesse;
- Riga 23: Implementazione del metodo [I1.getSomething] con la seguente firma:
String getSomething(double value);
[getSomething] accetta un parametro di tipo [double] e restituisce un risultato di tipo [String]. La sua implementazione lambda può essere quella alla riga 23 o quella alle righe 27–29. Nell'implementazione alla riga 23:
- il tipo del parametro [value] è omesso. Verrà quindi utilizzato il tipo [double] presente nella firma di [getSomething];
- il codice lambda non è racchiuso tra parentesi. Il risultato del lambda è quindi il valore della singola espressione in quel codice, in questo caso: String.format("ib2.lambda(%s)", value);
Nell'implementazione delle righe 27–29:
- il tipo del parametro [value] è dichiarato esplicitamente;
- utilizziamo un [return] per restituire il risultato della lambda. In questo caso, è necessario utilizzare le parentesi graffe;
- Righe 32–37: chiamiamo le varie funzioni anonime e lambda;
Il risultato ottenuto è il seguente:
4.2. Esempio-02 - L'interfaccia funzionale Predicate<T>
![]() |
Il più delle volte abbiamo a che fare con interfacce funzionali provenienti da librerie piuttosto che con interfacce funzionali definite da noi stessi. In questo caso, ci interessa l'interfaccia funzionale [Predicate] definita nel pacchetto [java.util.function], che contiene la maggior parte delle interfacce funzionali di Java 8. È definita come segue:

Abbiamo detto che un'interfaccia funzionale ha un solo metodo. Qui, tuttavia, ce ne sono quattro. Un'altra innovazione introdotta da Java 8 è stata il concetto di metodo predefinito in un'interfaccia, contrassegnato dalla parola chiave [default]. Qui abbiamo tre metodi di questo tipo. Questi metodi hanno la particolarità di avere un'implementazione predefinita. Non è quindi necessario che una classe che implementa un'interfaccia con metodi predefiniti li implementi. Pertanto, una classe che desidera implementare l'interfaccia [Predicate] ha un solo metodo che deve implementare: il metodo [test]. L'interfaccia [Predicate] è quindi effettivamente un'interfaccia funzionale. Possiamo quindi affermare che un'interfaccia funzionale è un'interfaccia la cui implementazione contiene un solo metodo obbligatorio. Se ha più di un metodo, gli altri devono avere la parola chiave [default].
Il metodo [Predicate<T>.test] accetta un parametro di tipo T e restituisce un valore booleano. Questa interfaccia viene generalmente utilizzata per filtrare le collezioni. Per illustrarne l'uso, utilizzeremo i seguenti dati:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// data
private String nom;
private int age;
private double poids;
private Sexe sexe;
// manufacturers
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// getters and setters
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- righe 32–38: il metodo [toString] restituisce la stringa JSON della persona;
La classe [People] definisce un elenco di 3 persone:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
public class Personnes {
private static List<Personne> personnes = Arrays.asList(new Personne("jean", Personne.Sexe.HOMME, 20, 70),
new Personne("marie", Personne.Sexe.FEMME, 10, 30), new Personne("camille", Personne.Sexe.FEMME, 30, 55));
public static List<Personne> get() {
return personnes;
}
public static String toString(List<Personne> liste) {
try {
return new ObjectMapper().writeValueAsString(liste);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- righe 10–11: l'elenco di 3 persone;
- righe 13–15: un metodo statico per recuperare questo elenco;
- righe 17–23: un metodo statico per ottenere la stringa JSON di un elenco di persone passato come parametro;
Questi dati saranno utilizzati dal codice seguente:
package dvp.java8.lambdas;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple02 {
public static void main(String[] args) {
// predicate implemented by anonymous class
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// predicate implemented by a lambda
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// list of persons
List<Personne> personnes = Personnes.get();
// displays
System.out.println(Personnes.toString(filterPersonnes(personnes, filterAge)));
System.out.println(Personnes.toString(filterPersonnes(personnes, filterPoids)));
}
private static List<Personne> filterPersonnes(List<Personne> personnes, Predicate<Personne> filter) {
// [filter] filters the [people] list
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- righe 13–18: implementazione dell'interfaccia Predicate<Person> utilizzando una classe anonima. Si tratta di un filtro basato sul peso della persona;
- riga 20: implementazione dell'interfaccia Predicate<Person> utilizzando una funzione lambda. Si tratta di un filtro basato sull'età della persona. In base a quanto detto, avrebbe potuto essere scritto anche come segue:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
ma la versione alla riga 20 è più concisa. Il tipo del parametro p viene dedotto dal contesto. Qui stiamo costruendo un tipo [Predicate<Person>]. Il metodo implementato ha quindi la firma [boolean test(Person param)]. Pertanto, il tipo implicito di p alla riga 20 è il tipo [Person];
- riga 22: recuperiamo un elenco predefinito di persone;
- riga 24: le filtriamo per età;
- riga 25: le filtriamo in base al peso. In entrambi i casi, visualizziamo la stringa JSON dell'elenco filtrato;
- righe 28–37: un metodo statico che
- accetta come parametri: un elenco di persone da filtrare e il filtro. Il filtro è un'istanza dell'interfaccia [Predicate<Person>]. Per comodità, qui e altrove nel documento ci riferiamo a un'istanza di un'interfaccia I come a un'istanza di una classe C che implementa I;
- restituisce l'elenco filtrato come risultato;
- riga 32: utilizziamo il metodo [test] dell'interfaccia [Predicate]. A seconda del filtro passato al metodo, il metodo [test] sarà:
return personne.getPoids()<50;
oppure
return p.getAge() < 28
L'esecuzione della classe [Exemple02] produce il seguente risultato:
4.3. Esempio-03 - L'interfaccia funzionale Function<T,R>
![]() |
L'interfaccia funzionale Function<T,R> è definita come segue:

L'unico metodo dell'interfaccia ha la firma R apply(T t). Viene generalmente utilizzato per creare un nuovo tipo Collection<R> a partire da un tipo Collection<T>. Per illustrare questa interfaccia, useremo il seguente codice:
package dvp.java8.lambdas;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Exemple03 {
public static void main(String[] args) throws JsonProcessingException {
// implementation with anonymous class
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// implementation with lambda
Function<Personne, Integer> mapToAge = p -> p.getAge();
// list of persons
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// displays
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// transformation List<Person> --> List<T>
private static <T> List<T> mapPersonnes(List<Personne> personnes, Function<Personne, T> mapper) {
List<T> maps = new ArrayList<>();
for (Personne p : personnes) {
maps.add(mapper.apply(p));
}
return maps;
}
}
- righe 15–20: implementiamo l'interfaccia [Function<Person, String>] con una classe anonima che esegue la trasformazione da Person a String;
- riga 22: implementiamo l'interfaccia [Function<Person, Integer>] con una funzione lambda che esegue la trasformazione da Person a Integer;
- Righe 33–39: un metodo statico che
- accetta due parametri: il primo, di tipo List<Person>, è un elenco di persone da trasformare. Il secondo, di tipo Function<Person, T>, è una funzione che prende ogni persona dall'elenco e crea un oggetto di tipo T;
- restituisce una List<T> in cui ogni elemento è il risultato della trasformazione da Person a T;
- righe 35–37: viene applicata la trasformazione Person -> T. Se il secondo parametro del metodo è l'oggetto [mapToName], viene eseguita una trasformazione Person -> String. Se è l'oggetto [mapToAge], viene eseguita una trasformazione Person -> Integer;
Il risultato è il seguente:
4.4. Esempio-04 - L'interfaccia funzionale Consumer<T>
![]() |
L'interfaccia funzionale Consumer<T> è definita come segue:

L'unico metodo dell'interfaccia ha la firma: void accept(T t). Questo metodo elabora (consuma) il proprio parametro e non restituisce alcun risultato. Per illustrare questo concetto, utilizzeremo il seguente codice:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.Consumer;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple04 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// immplémentaton lambda
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// displays
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- righe 14–19: l'interfaccia [Consumer<Person>] è implementata da una classe anonima il cui metodo [accept] visualizza il nome e l'età della persona;
- riga 21: l'interfaccia [Consumer<Person>] è implementata da una funzione lambda il cui metodo implicito [accept] visualizza il nome e il peso della persona;
- righe 23–25: l'elenco delle persone viene consumato dall'implementazione [consumerAge];
- righe 27–29: l'elenco delle persone viene utilizzato dall'implementazione [consumerWeight];
I risultati sono i seguenti:
4.5. Esempio-05 - L'interfaccia funzionale BiConsumer<T,U>
![]() |
L'interfaccia funzionale BiConsumer<T,U> è definita come segue:

L'unico metodo dell'interfaccia ha la firma: void accept(T t, U u). Consuma il tipo T con le informazioni aggiuntive U u. Ne illustreremo l'uso con il codice seguente:
package dvp.java8.lambdas;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.List;
import java.util.function.BiConsumer;
public class Exemple05 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
BiConsumer<Personne, Integer> biconsumerAge = new BiConsumer<Personne, Integer>() {
@Override
public void accept(Personne personne, Integer integer) {
personne.setAge(personne.getAge() + integer);
System.out.printf("age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// lambda implementation
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// displays
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- righe 14–20: implementazione dell'interfaccia BiConsumer<T,U> utilizzando una classe anonima. Il metodo [apply] utilizza il suo secondo parametro per aggiornare l'età della persona passata come primo parametro. Quindi visualizza il risultato;
- righe 22–25: implementazione dell'interfaccia BiConsumer<T,U> utilizzando una funzione lambda. Il metodo implicito [apply] utilizza il suo secondo parametro per aggiornare il peso della persona passata come primo parametro. Quindi visualizza il risultato;
- righe 27–29: l'elenco delle persone viene consumato utilizzando l'implementazione [biconsumerAge];
- righe 31–33: l'elenco delle persone viene elaborato utilizzando l'implementazione [biconsumerWeight];
I risultati ottenuti sono i seguenti:
4.6. Esempio-06 - L'interfaccia funzionale BiFunction<T,U,R>
![]() |
L'interfaccia funzionale BiFunction<T,U,R> è definita come segue:

L'unico metodo dell'interfaccia ha la firma: R apply(T t, U u). Questo metodo è simile al metodo [BiConsumer.apply], ma mentre quest'ultimo non restituisce un risultato, il metodo [BiFunction.apply] restituisce invece un risultato. Ne illustreremo l'uso con il codice seguente:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.BiFunction;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple06 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// lambda implementation
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// displays
for (Personne p : personnes) {
System.out.printf("age de %s = %s%n", p.getNom(), biFunctionAge.apply(p, 100));
}
System.out.println("--------");
for (Personne p : personnes) {
System.out.printf("poids de %s = %s%n", p.getNom(), biFunctionPoids.apply(p, 200));
}
}
}
- righe 14–19: L'interfaccia BiFunction<Person, Integer, Integer> è implementata utilizzando una classe anonima. Il suo metodo [apply] restituisce l'età della persona passata come primo parametro, aumentata del valore del secondo parametro;
- righe 21–23: l'interfaccia BiFunction<Person, Integer, Double> viene implementata utilizzando una funzione lambda. Il metodo [apply] restituisce il peso della persona passata come primo parametro, aumentato del valore del secondo parametro;
- righe 25–27: l'implementazione [biFunctionAge] viene applicata alle persone;
- righe 29–31: l'implementazione [biFunctionWeight] viene applicata alle persone;
I risultati ottenuti sono i seguenti:
age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0
Oltre alle funzioni lambda, Java 8 ha introdotto il tipo Stream<T>, che modella un flusso di elementi di tipo T. Questi elementi possono subire trasformazioni successive implementate da funzioni lambda. Quando possibile, e in presenza di più processori, queste trasformazioni possono talvolta essere eseguite in parallelo.
4.7. Esempio-07 - L'interfaccia funzionale Supplier<T>
![]() |
L'interfaccia funzionale Supplier<T> è definita come segue:

L'unico metodo dell'interfaccia ha la firma: T get(), il cui ruolo è quello di restituire un oggetto di tipo T.
Illustreremo questa interfaccia funzionale con il seguente codice:
package dvp.java8.lambdas;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple07 {
public static void main(String[] args) {
// anonymous implementation
Supplier<Personne> supplier = new Supplier<Personne>() {
// list of persons
List<Personne> personnes = Personnes.get();
// interface implementation
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// operation
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// person display
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- righe 13–28: implementazione di un tipo Supplier<Person>;
- righe 31–33: il metodo statico [display] richiede un parametro di tipo Supplier<Person>;
- righe 25–27: utilizzo dell'istanza Supplier<Person>;
Si ottengono i seguenti risultati:






