Java 8: lambda in 7 minuti (o quasi)

Think about it. You walk into a video store, you see 8-Minute Abs sittin’ there, there’s 7-Minute Abs right beside it. Which one are you gonna pick, man? I would go for the 7.

E’ ufficiale, Java 8 verrà rilasciato il 18 marzo di quest’anno e, come abbiamo detto in questo , grandi cambiamenti ci attendono. Il più importante tra questi è l’introduzione delle lambda expression, finalmente Java colma il divario con altri linguaggi introducendo le funzioni all’interno della sintassi. Sicuramente ci sono molti temi da trattare e un solo post non basta, per cui in questo articolo andiamo a scalfire la superficie partendo dai concetti di base, le lambda expression appunto. Una piccola avvertenza, a beneficio della semplicità, sacrificheremo alcuni dettagli che nei prossimi post ci ripromettiamo di ritornare ad approfondire.

Lambdache?

Quindi, cosa sono le lambda expression? Definiamo la lambda expression come funzioni anonime, sì funzioni come quelle del linguaggio C, per capirsi e anonime: senza, cioè, una dichiarazione che le dia un nome. Abbiamo, quindi, un nuovo approccio al codice, possiamo cioè scrivere codice funzionale: passare funzioni a funzioni così come adesso passiamo oggetti ad oggetti, restituire funzioni da funzioni, come adesso restituiamo oggetti a partire da oggetti ma soprattutto possiamo usare oggetti e funzioni assieme semplificando il codice. Detta così non è ancora chiara la portata della novità, per cui codice, codice, codice! Partiamo da come scriviamo il codice oggi e arriviamo a come si può scrivere con Java 8.

Lambda expressions are cool

In questo post faremo un unico esempio che andremo via via a modificare introducendo le lambda expression: abbiamo una lista di stringhe e vogliamo stamparla su console. Scriviamo il primo codice che ci viene in mente:

package it.cosenonjaviste.lambda;

import java.util.*;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      for (int i = 0; i < strings.size(); i++) {
         System.out.print(strings.get(i));
      }
   }

}

Quanto abbiamo scritto fa quello che ci aspettiamo, ma con l’uso dei generics possiamo fare di meglio:

package it.cosenonjaviste.lambda;

import java.util.*;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      for (String element : strings) {
         System.out.print(element);
      }
   }

}

Questo codice è sicuramente familiare, perché lo usiamo da anni e a colpo d’occhio sappiamo cosa fa. Siamo sicuri però che sia il codice più semplice da scrivere e quello più performante? Prima di procedere riflettiamo per un attimo su quello che abbiamo di fronte, siamo davanti ad un caso di iterazione esterna, cioè stiamo dicendo alla collezione di stringhe: dammi una stringa, poi dammene un’altra e un’altra ancora, è il codice client che chiede alla lista un elemento alla volta e poi decide cosa farne, in pieno stile imperativo.

Iterazione interna

L’interazione interna invece, è tutta un’altra storia e mi permetto di prendere in prestito l’esempio di al JavaOne per far capire la differenza tra i due. L’iterazione esterna è un po’ come far riordinare i giochi alla propria figlia piccolina:

  • “prendi quella palla laggiù”
  • “e adesso?”
  • “mettila nella cesta dei giochi”
  • “e adesso?”
  • “prendi quella bambola qui”
  • “e adesso?”
  • “mettila nella cesta dei giochi”
  • e adesso?

L’iterazione interna invece assomiglia di più a “prendi i tuoi giochi e mettili nella cesta” (sottointeso: scegli tu l’ordine con cui farlo e se hai l’altra mano libera e passi vicino ad un giocattolo, e ti va di prenderlo, prendi anche quello). Sicuramente anche chi non è genitore apprezza la compattezza e semplicità della seconda soluzione 😉 .

Cosa ci offre Java 8 per usare questo tipo di iterazione nel caso visto sopra? Il nuovo metodo forEach:

void    forEach(Consumer super T> action)
        //Performs an action for each element of this stream.

che prende in ingresso un Consumer, una nuova interfaccia di Java 8 (per la precisione è una interfaccia funzionale ma lasciamo questo argomento per un prossimo post). La cosa che ci interessa sapere ora è che questa interfaccia è come quelle che conosciamo e che ha un metodo da implementare:

void accept(T t) //Performs this operation on the given argument.

Scriviamo quindi una inner class anonima che implementa Consumer e la passiamo direttamente al forEach.

package it.cosenonjaviste.lambda;

import java.util.*;
import java.util.function.Consumer;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      strings.forEach(new Consumer() {
         public void accept(String s) {
            System.out.print(s);
         }
      });
   }
}

Il risultato non cambia, ma quello che abbiamo fatto è un cambio di paradigma, non diciamo più alla lista come produrre il risultato, ma cosa voglia che venga fatto su ogni elemento della collezione, ci concentriamo meno sul modo per farlo ma su cosa vogliamo ottenere. Un altro vantaggio è che l’iterazione è nascosta nell’implementazione. Non solo non rischiamo più di commettere errori nella costruzione del ciclo, ma l’implementazione è polimorfica in base alla classe a cui si applica il metodo. Di conseguenza, l’iterazione interna può essere ottimizzata a seconda del tipo di struttura dati e, quando richiesto e dove applicabile, può essere svolta in parallelo.

Basta un poco di zucchero e..

Tuttavia, il codice che abbiamo adesso non sembra così invitante: abbiamo scritto molte più righe per ottenere lo stesso risultato di prima. Fortunatamente, possiamo sostituire la inner class con una lambda expression, liberandoci di tutto il codice in più e, di fatto, scrivendo tutto in un’unica riga.

import java.util.*;
import java.util.function.Consumer;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      strings.forEach((String s) -> System.out.print(s));

   }
}

Adesso sì che abbiamo qualcosa di un po’ più “alieno” sotto gli occhi. Esaminiamo la nostra lambda (=funzione anonima) con attenzione. Una funzione ha un nome, una lista di parametri, un corpo e un valore ritornato. Abbiamo la lista di parametri (String s) separata dal corpo (System.out.print(s)) con la freccia ->. Il tipo di ritorno viene dedotto dal contesto, in questo caso è void. Il nome invece non c’è: era anonima la inner class prima, è anonima la funzione adesso. Ora la definizione di lambda che abbiamo dato all’inizio ha più senso.

Quindi, dove possiamo utilizzare le lambda? Dovunque ci sia una interfaccia che preveda tra i suoi metodi uno e un solo metodo astratto. Beh, ma le interfacce non hanno implementazioni, tutti i metodi di una interfaccia sono astratti per definizione, direte. No, da Java 8 è possibile avere interfacce che hanno dei metodi implementati (detti di default), anche questo argomento lo lasciamo per un prossimo post.

Meno verboso, meno noioso

Si può fare ancora di meglio, dal contesto il compilatore sa che stiamo applicando la nostra lambda ad una collezione di stringhe attraverso la type inference, quindi possiamo semplificare la funzione omettendo il tipo per s.

package it.cosenonjaviste.lambda;

import java.util.*;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      strings.forEach(s -> System.out.print(s));

   }
}

Ora la nostra lambda è talmente semplice che.. si può semplificare ancora! Dato che l’unica cosa che facciamo al suo interno è chiamare un metodo, possiamo usare un method reference al posto della nostra espressione. Anche questa è una novità introdotta dalla versione 8. Possiamo pensare i method reference come delle lambda expression che non sono anonime, ma che si riferiscono ad un specifico metodo di una determinata classe (o di una istanza). Ad esempio, String::valueOf o Integer::compare sono esempi di method reference per metodi statici. Detto questo, non ci resta che usare questo nuovo costrutto nel nostro codice e modificare il nostro esempio per l’ultima volta.

package it.cosenonjaviste.lambda;

import java.util.*;

public class SevenMinutesLambda {

   public static void main(String[] args) {

      List strings = Arrays.asList("Lambda ", "expressions ", "are ", "cool");

      strings.forEach(System.out::print);

   }
}

Il passaggio alle lambda è completo ora, siamo passati da un ciclo for, classico esempio di iterazione esterna, a una iterazione interna gestita attraverso una funzione anonima o un method reference, sicuramente un bel po’ della verbosità di Java è stata eliminata a vantaggio della chiarezza e semplicità.

Conclusioni

Abbiamo ancora molto da dire sulle lambda expression, questo post è servito giusto a rompere il ghiaccio, ma abbiamo delineato il cambiamento fondamentale di Java 8. Volutamente abbiamo tralasciato i dettagli sugli stream, le functional interface, i method reference e i default method delle interfacce; saranno argomento dei prossimi post.

Chi volesse provare Java 8 già adesso, può scaricare la Developer Preview del JDK e installare NetBeans o Eclipse con supporto a Java 8. Un’ultima nota tecnica: Java 8 non sarà disponibile per Windows XP.

Giampaolo Trapasso

Sono laureato in Informatica e attualmente lavoro come Software Engineer in Databiz Srl. Mi diverto a programmare usando Java e Scala, Akka, RxJava e Cassandra. Qui mio modesto contributo su StackOverflow e il mio account su GitHub

  • Maddalena Barlotti

    Molto interessante, aspetto il seguito sulle altre novità di Java 8! 🙂

    • Giampaolo Trapasso

      Grazie! Ci sto lavorando 🙂

  • Tommy G.

    Articolo fantastico, Io non vedo l’ora anche di Java 9 per l’esecuzione del codice su GPU, sa cosa non viene fuori!. Almeno viene abbattuto la lantenza della JVM rispetto agli altri linguaggi..

    • Giampaolo Trapasso

      Ti ringrazio e tienici d’occhio. Altri post sull’argomento in arrivo.

    • Giampaolo Trapasso

      No, non sapevo di lombok. Ma visto che tu lo conosci bene, che ne dici di scrivere un post e presentarcelo qui su CNJ? Magari potresti anche vincere il nostro concorso 🙂

  • Marco

    Ciao Giampaolo, davvero molto interessante questa novità in Java 8, bell’articolo.

    • Giampaolo Trapasso

      Grazie Marco.

  • David Corsalini

    Lo sapevo che facevo bene a spulciare il blog, bell’articolo, forse inizierò a capire qualcosa anche di RxJava!

    • Giampaolo Trapasso

      Grazie!

    • Giampaolo Trapasso

      Ora qualcosa su RxJava lo trovi anche qui su CNJ 😉

  • Pingback: ()

  • Pingback: ()

  • Pingback: ()

  • Pingback: ()

  • M

    Bell’articolo, segnalo imperfezione:

    Alla riga 12 del primo esempio con lambda
    …strings.forEach(new Consumer() {…

    Comsumer è templatico, dovrebbe essere
    …strings.forEach(new Consumer() {

    • Giampaolo Trapasso

      Grazie del complimento e grazie per la segnalazione, ho corretto.

  • Pingback: ()

  • Ettore Riva

    scusate, a me non è chiara una cosa:
    nel method reference come faccio a capire i parametri che sto passando al metodo?

    • Giampaolo Trapasso

      Non so se riesco a spiegarmi bene in un commento, proviamoci. Tutto dipende dal contesto in cui viene chiamata la lambda cioè dal tipo di interfaccia funzionale che stiamo usando. Prendiamo l’esempio del post: System.out::println è solo zucchero sintattico per s -> System.out.println(s) e a sua volta è abbreviazione di (String s) -> System.out.println(s) perché il compilatore sa che stiamo lavorando su di una lista di stringhe. Ma cosa avviene dietro alla quinte?

      Prendiamo la firma di forEach che è public void forEach(Consumer action) dove E è il tipo della lista. La lambda (o il method reference) che passiamo deve fare “match” con l’interfaccia funzionale Consumer.

      Questo vuol dire che stiamo implementando l’unico metodo astratto di Consumer che è void accept(T t) dove T è il tipo del Consumer, quindi ancora String. Il metodo forEach non fa altro che chiamare accept del Consumer su di ogni elemento della lista.

      Se metto assieme tutti i pezzi sto dicendo al compilatore di usare il metodo println di System.out come metodo del Consumer da applicare a tutte le stringhe della lista. Complicato? No, ma con alcuni inscatolamenti che proprio le lambda nascondono. Dove trovi un Consumer, d’ora in poi sai che devi passare una lambda che prende un argomento e non restituisce niente oppure un method reference di un metodo che prende un argomento e torna void. Visto il giro nel dettaglio qualche volta, poi lo si può dimenticare e usare le lambda come se ci fossero sempre state. E’ cura del compilatore segnalarti se qualche tipo non gli torna.

      Spero di essere riuscito a spiegare meglio, fammi sapere.

      • Ettore Riva

        grazie per la risposta. se non riesco a chiarire le idee ti chierderò ancora

  • Pingback: ()

  • Carmelo Iriti

    Bel post!

    • Giampaolo Trapasso

      Grazie Carmelo! Se c’è qualche argomento che ti piacerebbe veder trattato qui.. non hai che da chiedere 🙂

  • etto

    Salve, non è che java si sta riprendendo un po alla volta il native C e tra un po dovremmo ristudiare l’aritmetica dei puntatori !
    Vorrei conoscere al di la della battuta, il funzionamento della grabage collector e il linguaggio macchina o bytecode di java, conoscete qualche buon libro o sito che ne tratti in dettaglio?
    ps. si, lo so che vado in cerca del puro astrattismo 😉 dopo tutto e da li che è nato tutto…..

    • etto

      Grazie Gianpaolo per tuo articolo.