Come utilizzare Lambda in Java

28 feb 2024 7 min di lettura
Come utilizzare Lambda in Java
Indice dei contenuti

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:

Java (versione 11 o successiva) installato sul tuo computer, con il compilatore fornito dal Java Development Kit (JDK).

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 ( StringInteger, ecc.) può essere dedotto automaticamente dal compilatore in modo da non doverlo dichiarare.
  • 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 la richiesta jshell> e premendo ENTER. Per uscire jshelldigitare /exit.

Ecco un esempio di espressione lambda:

(x) -> System.out.println(x)

In questa lambda, xè il parametro che viene stampato con System.out.println()metodo. Un buon utilizzo di tale lambda è stampare il contenuto di un elenco fornito dal metodo foreach. Per testarlo, jshellcrea 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 della lista 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

La lambda (x) -> System.out.println(x)può essere ulteriormente semplificata perché ha un solo parametro. In questo caso, puoi:

  • rimuovere le parentesi attorno al parametro x.
  • rimuovere xdel tutto il parametro e utilizzare solo il riferimento al metodo System.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 sezione successiva imparerai come utilizzare le lambda integrate disponibili nel pacchetto java.util.function.

Utilizzo di Lambda integrati

Il pacchetto java.util.function contiene una serie di interfacce funzionali che coprono casi d'uso comuni. Per utilizzare una di queste interfacce è necessario fornire l'implementazione del suo metodo astratto. Vediamo come è possibile farlo per alcune delle interfacce più utili.

Predicati

predicateè un lambda per testare una condizione. Ha un metodo testche restituisce un valore booleano: vero se la condizione è soddisfatta e falso 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 diamante <>.
  • Con il segno uguale il predicato viene assegnato a un'espressione lambda che controlla se l'argomento passato xinizia con la lettera D.
  • Nella seconda riga inizi iterando l'elenco degli animali domestici utilizzando il suo stream()metodo.
  • Successivamente nella seconda riga segue il filtermetodo utilizzato per filtrare i valori in base a un predicato. Il definito in precedenza filterPetsviene passato a questo metodo per questo scopo.
  • Infine, utilizzi il forEachmetodo per scorrere i valori filtrati e stamparli ciascuno utilizzando il metodo reference System.out::println.

L'output jshell sarà simile al seguente:

filterPets ==> $Lambda$32/0x0000...
Dog

La prima riga può essere ignorata. Conferma che un riferimento filterPetsè stato creato e viene fornita la sua posizione di memoria. Vedrai tali righe jshellquando 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 predicate. Tuttavia, in pratica, può essere scritto in modo molto più semplice. Una volta definito predicate, puoi usarlo direttamente senza passargli esplicitamente argomenti in un 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 ha senso filterPets se si intende utilizzarlo più di una volta nel 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

consumer viene utilizzato per consumare un valore. Ha un metodo acceptche 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 file 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

functionviene utilizzato per trasformare un valore passato come argomento. Ha un metodo applyche 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 si definisce a functionè necessario specificare i tipi di input e output. Nel nostro esempio, entrambe sono stringhe. Ecco perché la prima riga inizia con Function<String, String>per dichiarare il riferimento toUpperCase. Dopo il segno di uguale segue l'espressione lambda in cui il metodo stringa toUpperCasegioca il ruolo chiave modificando la stringa di input in maiuscolo.

Sulla seconda riga viene trasmesso in streaming l'elenco degli animali domestici e mapviene richiamato il metodo. Questo metodo viene utilizzato per trasformare ogni animale domestico in maiuscolo con la lambda toUpperCase. Infine, usi il metodo forEach per stampare ciascun animale domestico. L' jshelloutput sarà:

DOG
CAT

Fornitori

supplierviene utilizzato per fornire un valore al chiamante e non accetta argomenti. Ha un metodo getche restituisce il valore desiderato. Ecco un supplieresempio che fornisce l'ora corrente:

Supplier<java.time.LocalTime> timeSupplier = () -> java.time.LocalTime.now();
System.out.println(timeSupplier.get());

Nella prima riga si definisce un fornitore per <java.time.LocalTime>i tipi denominati timeSupplier. È assegnato a un lambda che non ha parametri e il suo codice di esecuzione restituisce l'ora corrente fornita dal metodo integrato java.time.LocalTime.now. Infine, utilizzi il getmetodo per ottenere il valore del lambda timeSupplier e stamparlo con System.out.println.

Quando esegui il codice sopra jshellvedrai l'ora corrente stampata in questo modo:

21:52:38.384278

Operatori unari

Analogamente functionunaryOperatoranche a viene utilizzato per trasformare un valore passato come argomento. Tuttavia, con a unaryOperatori tipi di input e output sono sempre gli stessi. Viene chiamato anche il suo metodo applye restituisce il valore trasformato. Ecco come puoi utilizzare unaryOperatorper trasformare una stringa come "dog" in maiuscolo:

UnaryOperator<String> toUpperCase = x -> x.toUpperCase();
System.out.println(toUpperCase.apply("dog"));

Nell'esempio precedente, definisci una unaryOperatorchiamata toUpperCaseche 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 jshellvedrai la stringa "DOG" stampata in questo modo:

DOG

Operatori binari

binaryOperatorviene utilizzato per combinare due valori. Ha anche un metodo applyche 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 una binaryOperatorchiamata additionche accetta argomenti interi e restituisce la loro somma. Quindi usi il applymetodo per sommare i numeri 2 e 3 e stampare il risultato con System.out.println. Quando esegui il codice sopra jshellvedrai:

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 funzioni 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.

Support us with a

Successivamente, completa il checkout per l'accesso completo a Noviello.it.
Bentornato! Accesso eseguito correttamente.
Ti sei abbonato con successo a Noviello.it.
Successo! Il tuo account è completamente attivato, ora hai accesso a tutti i contenuti.
Operazione riuscita. Le tue informazioni di fatturazione sono state aggiornate.
La tua fatturazione non è stata aggiornata.