Come utilizzare Lambda in Java
L'autore ha selezionato il fondo gratuito e open source per ricevere una donazione come parte del programma Write for DOnations.
introduzione
I lambda, noti anche come funzioni e chiusure anonime, sono blocchi di codice che possono essere passati ed eseguiti successivamente. I lambda sono presenti in Java e nella maggior parte dei linguaggi di programmazione moderni per consentire la scrittura di codice più conciso con meno standard.
In questo tutorial imparerai a scrivere le tue espressioni lambda. Imparerai anche a utilizzare alcuni Lambda integrati disponibili nel pacchetto java.util.function
. Coprono casi d'uso comuni e semplificano l'avvio con lambda.
Prerequisiti
Per seguire questo tutorial avrai bisogno di:
Un ambiente in cui è possibile eseguire programmi Java da seguire insieme agli esempi. Per configurarlo sul tuo computer locale, avrai bisogno di quanto segue:
- Java (versione 11 o successiva) installato sul tuo computer, con il compilatore fornito dal Java Development Kit (JDK). Per Ubuntu e Debian, segui i passaggi per l'Opzione 1 nel nostro tutorial Come installare Java con Apt su Ubuntu 22.04. Per altri sistemi operativi, inclusi Mac e Windows, seguire le opzioni di download per l'installazione di Java.
- Per compilare ed eseguire gli esempi di codice, questo tutorial utilizza Java Shell, noto anche come JShell, che è un Read-Evaluate-Print Loop (REPL) eseguito dalla riga di comando. Per iniziare con JShell, consulta la guida Introduzione a JShell.
Familiarità con Java e programmazione orientata agli oggetti, che puoi trovare nel nostro tutorial Come scrivere il tuo primo programma in Java.
Una comprensione dei tipi di dati Java, discussa nel nostro tutorial Capire i tipi di dati in Java.
Una comprensione degli elenchi Java, su cui si basano molti esempi. Puoi trovare ulteriori informazioni sugli elenchi in Come utilizzare gli elenchi in Java.
Creare la tua Lambda
Per creare la tua espressione lambda, devi prima comprenderne la struttura. Può essere descritto come segue:
(argument(s)) -> {body}
Ci sono tre parti in un'espressione lambda:
- Un elenco di zero o più argomenti separati da virgole, racchiusi tra parentesi. Il loro tipo (
String
,Integer
, ecc.) può essere dedotto automaticamente dal compilatore, quindi non è necessario dichiararlo. - La freccia
-> . Viene utilizzato per collegare i parametri e il corpo dell'espressione lambda. - Un corpo che contiene il codice da eseguire. Se sono presenti argomenti, vengono utilizzati nel corpo per eseguire operazioni con essi.
Informazioni: per seguire il codice di esempio in questo tutorial, apri lo strumento Java Shell sul tuo sistema locale eseguendo il comando jshell
. Quindi puoi copiare, incollare o modificare gli esempi aggiungendoli dopo il prompt jshell>
e premendo INVIO
. Per uscire da jshell
, digita /exit
.
Ecco un esempio di espressione lambda:
(x) -> System.out.println(x)
In questo lambda, x
è il parametro che viene stampato con il metodo System.out.println()
. Un buon utilizzo di tale lambda è stampare il contenuto di un elenco fornito dal metodo foreach. Per testarlo, in jshell
crea un elenco di animali domestici come questo:
List<String> pets = Arrays.asList("Dog", "Cat")
Vedrai una conferma che la lista è stata creata:
pets ==> [Dog, Cat]
Ora puoi stampare il contenuto dell'elenco utilizzando il metodo foreach
e l'espressione lambda:
pets.forEach((x) -> System.out.println(x))
Vedrai ogni animale domestico stampato su una nuova riga:
Dog
Cat
Il lambda (x) -> System.out.println(x)
può essere ulteriormente semplificato perché ha un solo parametro. In questo caso puoi:
- rimuovere le parentesi attorno al parametro
x
. - rimuovere completamente il parametro
x
e utilizzare solo il riferimento al metodoSystem.out::println
. I riferimenti ai metodi sono una notazione abbreviata per un'espressione lambda per chiamare un metodo. Sono considerati più leggibili e consigliati dalle migliori pratiche del codice pulito.
Pertanto, lo stesso codice può essere riscritto come:
pets.forEach(System.out::println)
Tuttavia, non tutte le lambda sono così semplici e possono essere montate su un'unica linea. Probabilmente dovrai utilizzare lambda con più parametri e il blocco di esecuzione sarà su più di una sola riga. È allora che la sintassi diventa più complessa. Ad esempio, aggiungiamo anche il numero di animali durante la stampa:
pets.forEach(x -> {
System.out.println("Pet name: " + x);
System.out.println("Pet number: " + pets.indexOf(x));
});
Nota come l'espressione lambda è ora racchiusa tra parentesi graffe aggiuntive {}
perché si trova su più di una riga. Inoltre, per ottenere l'indice dell'animale nell'elenco, devi utilizzare il metodo indexOf
dell'elenco e passargli il nome dell'animale come parametro pets.indexOf(x) .
Il codice sopra produrrà il seguente output:
Pet name: Dog
Pet number: 0
Pet name: Cat
Pet number: 1
Ecco come puoi scrivere le tue espressioni lambda in Java. Nella prossima sezione imparerai come utilizzare le lambda integrate disponibili nel pacchetto java.util.function
.
Utilizzo di Lambda integrati
Il pacchetto java.util.function
contiene un insieme di interfacce funzionali che coprono casi d'uso comuni. Per utilizzare una di queste interfacce è necessario fornire l'implementazione del relativo metodo astratto. Vediamo come è possibile farlo per alcune delle interfacce più utili.
Predicati
Un predicato
è un lambda per testare una condizione. Ha un metodo test
che restituisce un valore booleano: true se la condizione è soddisfatta e false in caso contrario. Ad esempio, puoi verificare se i valori di un elenco di stringhe iniziano con una lettera specifica. Usiamo nuovamente l'elenco degli animali domestici e stampiamo il nome di ciascun animale domestico che inizia con la lettera "D":
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(t -> filterPets.test(t)).forEach(System.out::println);
Il codice può essere scomposto come segue:
- Nella prima riga crei un tipo di predicato per i valori stringa chiamato
filterPets
. Il tipo di stringa è definito all'interno dell'operatore rombo<>
. - Con il segno uguale il predicato viene assegnato a un'espressione lambda che controlla se l'argomento passato
x
inizia con la lettera D. - Nella seconda riga inizi iterando l'elenco degli animali domestici utilizzando il suo metodo
stream()
. - Successivamente nella seconda riga segue il metodo
filter
che viene utilizzato per filtrare i valori in base a un predicato. IlfilterPets
precedentemente definito viene passato a questo metodo per questo scopo. - Infine, utilizzi il metodo
forEach
per scorrere i valori filtrati e stamparli ciascuno utilizzando il riferimento al metodoSystem.out::println
.
L'output di jshell
sarà simile al seguente:
filterPets ==> $Lambda$32/0x0000...
Dog
La prima riga può essere ignorata. Conferma che è stato creato un riferimento filterPets
e viene fornita la sua posizione di memoria. Vedrai tali righe in jshell
quando dichiari lambda. Puoi ignorarli per ora perché sono utili solo per il debug.
La seconda riga stampa il nome dell'unico animale domestico che inizia con la lettera D - Cane.
L'esempio esplicito sopra è utile per apprendere e comprendere come funziona un predicato
. Tuttavia, in pratica, può essere scritto in modo molto più semplice. Una volta definito il predicato
, puoi usarlo direttamente senza passargli esplicitamente argomenti in una lambda. Questo perché ogni predicato, proprio come ogni lambda, ha un solo metodo. Java può capire automaticamente che questo metodo deve essere chiamato con l'unico parametro disponibile. Quindi lo stesso codice può essere riscritto come:
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(filterPets).forEach(System.out::println);
Definire il predicato separatamente e assegnarlo a un oggetto come filterPets
ha senso se intendi utilizzarlo più di una volta nel tuo codice. Tuttavia, se filtrerai gli animali domestici in base alla loro prima lettera solo una volta, avrà più senso scrivere il predicato direttamente nel metodo di filtro in questo modo:
pets.stream().filter(x -> x.startsWith("D")).forEach(System.out::println);
In tutti i casi, l'output sarà lo stesso. La differenza sta nel quanto esplicito e riutilizzabile vuoi che sia il tuo codice. Questo sarà valido per il resto degli esempi in questo tutorial.
Consumatori
Un consumatore
viene utilizzato per consumare un valore. Ha un metodo accept
che restituisce void, cioè non restituisce nulla. Senza rendertene conto, quando hai creato la tua prima lambda per stampare l'elenco degli animali domestici, hai implementato un consumer
.
Definiamo un consumatore in modo più esplicito in questo modo:
Consumer<String> printPet = x -> System.out.println(x);
pets.forEach(printPet);
Come previsto, l'output elencherà i due animali domestici in questo modo:
Dog
Cat
Proprio come nel predicato lambda, quanto sopra è il modo più esplicito per definire e utilizzare un consumatore in modo da poterlo comprendere al meglio.
Funzioni
Una funzione
viene utilizzata per trasformare un valore che viene passato come argomento. Ha un metodo apply
che restituisce il valore trasformato. Ad esempio, puoi utilizzare una funzione per trasformare ciascuno degli animali domestici e renderlo in maiuscolo:
Function<String, String> toUpperCase = x -> x.toUpperCase();
pets.stream().map(x -> toUpperCase.apply(x)).forEach(System.out::println);
Quando definisci una funzione
devi specificare i tipi di input e output. Nel nostro esempio, entrambe sono stringhe. Ecco perché la prima riga inizia con Function
per dichiarare il riferimento toUpperCase
. Dopo il segno di uguale segue l'espressione lambda in cui il metodo di stringa toUpperCase
svolge il ruolo chiave modificando la stringa di input in maiuscolo.
Sulla seconda riga viene trasmesso in streaming l'elenco degli animali domestici e viene invocato il metodo map
. Questo metodo viene utilizzato per trasformare ogni animale domestico in maiuscolo con la lambda toUpperCase
. Infine, utilizzi il metodo forEach
per stampare ciascun animale domestico. L'output di jshell
sarà:
DOG
CAT
Fornitori
Un supplier
viene utilizzato per fornire un valore al chiamante e non accetta argomenti. Ha un metodo get
che restituisce il valore desiderato. Ecco un esempio di fornitore
che fornisce l'ora corrente:
Supplier<java.time.LocalTime> timeSupplier = () -> java.time.LocalTime.now();
System.out.println(timeSupplier.get());
Nella prima riga definisci un fornitore per i tipi
chiamato timeSupplier
. È assegnato a un lambda che non ha parametri e il suo codice di esecuzione restituisce l'ora corrente fornita dal metodo java.time.LocalTime.now
integrato. Infine, utilizzi il metodo get
per ottenere il valore del lambda timeSupplier e stamparlo con System.out.println
.
Quando esegui il codice sopra in jshell
vedrai l'ora corrente stampata in questo modo:
21:52:38.384278
Operatori unari
Analogamente a una funzione
, anche un unaryOperator
viene utilizzato per trasformare un valore passato come argomento. Tuttavia, con un unaryOperator
i tipi di input e output sono sempre gli stessi. Il suo metodo è anche chiamato apply
e restituisce il valore trasformato. Ecco come puoi utilizzare un unaryOperator
per trasformare una stringa come "dog" in maiuscolo:
UnaryOperator<String> toUpperCase = x -> x.toUpperCase();
System.out.println(toUpperCase.apply("dog"));
Nell'esempio precedente, definisci un unaryOperator
chiamato toUpperCase
che accetta una stringa e restituisce un'altra stringa in maiuscolo. Quindi usi il metodo apply
per trasformare la stringa “dog ” in “DOG ” e stamparla con System.out.println
. Quando esegui il codice sopra in jshell
vedrai la stringa "DOG" stampata in questo modo:
DOG
Operatori binari
Un binaryOperator
viene utilizzato per combinare due valori. Ha anche un metodo apply
che restituisce il valore combinato. Ad esempio, puoi creare una calcolatrice che somma due numeri come questo:
BinaryOperator<Integer> addition = (x, y) -> x + y;
System.out.println(add.apply(2, 3));
La prima riga definisce un binaryOperator
chiamato addition
che accetta argomenti interi e restituisce la loro somma. Quindi usi il metodo apply
per aggiungere i numeri 2 e 3 e stampare il risultato con System.out.println
. Quando esegui il codice sopra in jshell
vedrai:
add ==> $Lambda$29/0x000...
5
In questo modo puoi utilizzare le interfacce lambda integrate disponibili nel pacchetto java.util.function
per realizzare utili implementazioni e creare utili snippet di codice.
Conclusione
In questo tutorial hai imparato come scrivere le tue espressioni lambda in Java. Hai anche imparato come utilizzare le lambda integrate disponibili nel pacchetto java.util.function
che copre scenari comuni. Ora puoi utilizzare questi lambda per rendere il tuo codice più pulito e leggibile.