Publié par

Il y a 8 années -

Temps de lecture 5 minutes

Les lambda expressions dans Java 8

Durant cet été, l’actuel architecte de Java auprès d’Oracle, Brian Goetz, a fourni des informations intéressantes sur l’implémentation des lambda expressions dans le futur Java 8 et de ses conséquences sur le langage. Par lambda expression comprenez ici closure ou fonction anonyme, qu’il est possible de stocker dans une variable ou de retourner depuis une méthode ou depuis une autre fonction, et bien sûr d’appeler. L’intégration des lambda expressions se fait dans le cadre de la JSR 335 (JSR 335: Lambda Expressions for the JavaTM Programming Language), aussi appelé Lambda Project.

Du point vue d’Oracle, la stratégie donnée à Brian est celle-ci :

Oracle’s position is that Java must evolve – carefully, of course – in order to remain competitive. (« La position d’Oracle est que Java doit évoluer – prudemment, bien sûr – afin de rester compétitif. »)

À partir de quoi, Brian propose :

It is my belief that the best direction for evolving Java is to encourage a more functional style of programming. (« Je suis convaincu que la meilleure orientation pour l’évolution de Java est d’encourager un style de programmation plus fonctionnel. »)

Nous allons voir dans cet article, l’état des actuelles propositions faites pour étendre le langage Java vers un style fonctionnel.

Avertissement : Les informations présentes dans cet article sont celles qui ont été récupérées en date de publication de l’article. Tant que Java 8 n’est pas officiellement sorti, ces informations peuvent changer à tout instant.

De la nature des lambda expressions

La lambda expression est simplement une fonction anonyme, aussi appelée closure. Il est possible de les stocker dans des variables, définir des fonctions qui prennent en entrée d’autres fonctions et/ou retournent une fonction (voir l’article Comparaison d’API Java de programmation fonctionnelle). Les lambda expressions sont à la base de la programmation fonctionnelle.

Pour représenter les lambda expressions dans Java, Brian Goetz propose deux formes possibles : lambda-en-tant-qu’objet et lambda-en-tant-que-fonction. Le point de vue lambda-en-tant-qu’objet consiste à disposer d’une interface ou d’une classe représentant les fonctions, ce que Brian appelle le type fonction. Dans cette perspective, on aura des fonctions dérivant par exemple de l’interface suivante :

interface Function<T, R> {
    R apply(T arg);
}

Cette présentation des lambda expressions correspond à celle employée par Guava notamment. Mais du fait du type erasure, on obtient au niveau du bytecode quelque chose comme :

interface Function {
    Object apply(Object arg);
}

On perd ici toute information concernant le type d’entrée et de sortie de la fonction.

En fait, Brian préfère nous diriger vers une approche où les lambda expressions sont des éléments du langage à part entière et non des objets. Pour lui, la solution lambda-en-tant-qu’objet aurait été envisageable dans le cas où Java aurait permis la réification des types paramètres dans les types génériques, ou pour être plus exact la conservation des informations du type paramètre au niveau du bytecode. Mais comme Java applique le type erasure (ie. l’effacement du type paramètre au niveau du bytecode), Brian Goetz pense qu’il est préférable de se tourner vers la solution lambda-en-tant-que-fonction.

Reste à savoir ce que cela donnera au niveau du bytecode.

Syntaxe des lambda expressions

La solution retenue pour représenter les lambda expressions est basée sur la syntaxe C#. Ainsi, la représentation d’expression simple comme x+1 ou x+y pourrait s’écrire :

x => x + 1
(x) => x + 1
(int x) => x + 1
(x, y) => x + y
(int x, int y) => x + y

Sachant que la syntaxe n’est encore complètement décidée dans les détails, on pourrait très bien avoir :

x -> x + 1
(int x, int y) -> x + y
...

Dans le cas où vous voulez écrire votre fonction sur plusieurs lignes, vous devrez l’écrire ainsi :

(int x) => {
    if (x >= 18) System.out.println("majeur");
    else System.out.println("mineur");
}

Il reste à décider d’éléments de détail comme de la forme de la flêche, la façon d’écrire une lambda expression sans paramètre, l’obligation de mettre un return dans une lambda de plusieurs lignes quand elle retourne une valeur, etc.

Conclusion

La syntaxe choisie pour les lambda expressions est relativement claire et accessible. Reste à déterminer la syntaxe utilisée pour indiquer qu’une méthode ou une lambda prend en paramètre une autre lambda. Quoiqu’il en soit, l’arrivée de la programmation fonctionnelle dans Java ouvre des portes intéressantes tant le sujet est riche (nouveaux opérateurs sur les collections, collection infinie, maîtrise du futur d’une fonction, etc.). Mais en attendant, l’une des premières conséquences de l’arrivée des lambda expressions est la mise à jour de l’API Collection. Nous verrons dans un prochain article la nouveauté que sont les méthodes virtuelle d’extension, pour permettre à Java d’ajouter de nouveaux opérateurs dans l’API Collection en minimisant l’effort de développement.

Références

Publié par

Publié par François Sarradin

Consultant Java et λ développeur. Blog personnel : http://kerflyn.wordpress.com/ Twitter : @fsarradin

Commentaire

10 réponses pour " Les lambda expressions dans Java 8 "

  1. Publié par , Il y a 8 années

    Cela m’a fait sourire et fait plaisir de voir que les lambda expressions en Java allaient faire place à une dose d’inférence de type.
    Cela me semblait inévitable: http://www.jroller.com/dmdevito/entry/facing_the_wall_of_closure

    Enfin, un peu d’inférence de type, au sein d’un des langages parmi les plus utilisés (hors la sphère Microsoft).
    J’espère que cela pourra donner des idées à d’autres personnes, pour l’évolution de Java.

  2. Publié par , Il y a 8 années

    @Dominique: l’inférence de type existe déjà dans Java. Il est utiliser pour simplifier l’utilisation des types generics. Scala d’ailleurs utilise ce moteur afin de permettre la simplification des déclarations. Par contre, je ne sais pas si des efforts sont possibles et seront effectués pour améliorer l’algorithme utilisé. Attendons de voir ce qui découlera des premières implémentations…

  3. Publié par , Il y a 8 années

    @François
    Je suis loin de bien connaitre les generics… où se situe l’inférence de type dans l’utilisation des generics ?
    Merci

  4. Publié par , Il y a 8 années

    Est-ce qu’on pourra utiliser des lambdas à la place des classes anonymes?
    Typiquement dans une appli swing, une appli android ou wicket, on a plein d’implémentations pour un bouton, un composant graphique, où les lambdas seraient bien agréables et plus légères. De même on aura bien la notion de closure? Les variables de la méthode contenant la lambda pourront être reprises dans la lambda?

  5. Publié par , Il y a 8 années

    @Dominique: tu pourras trouver dans ce lien un cas où l’inférence de type de Java s’emmêle les pinceaux : http://www.developpez.net/forums/d850489/java/general-java/langage/generics-generics-correspondance-type/

    Ce qu’il faut retenir, c’est qu’écrire quelque chose comme Arrays.asList(1, 2.0) ne se fait pas gratuitement. Java passe par l’inférence de type pour le traduire en Arrays.asList(1, 2.0). Pour rappel, la méthode asList() est une méthode générique dont la signature est

    public static List asList(T… a)

  6. Publié par , Il y a 8 années

    @Gabriel: je ne sais pas ce qui est décidé au niveau du JCP. De mon avis, il y a fort à parier que ce sera aux API et frameworks de proposer des adaptations, même si les méthodes virtuelles d’extension leur mâche le travail.

    Pour ta 2e question, j’espère aussi que les lambda de Java sont des closures et j’ai du mal à imaginer le contraire. Sinon, comment lier les variables libres d’une lambda ? Ca ne fait aucun sens de les sortir de leur contexte initial.

  7. Publié par , Il y a 8 années

    On y vient enfin ! Le sujet avait déjà été débattu (et certains ont dû en perdre des membres) durant le développement de Java 7. Pour preuve, un bel article sur ce même blog http://blog.xebia.fr/2007/05/10/des-closures-en-java/.

    Il faut espérer que cela arrive désormais rapidement afin de casser l’habitude de l’héritage intempestif au profit de la composition.

  8. Publié par , Il y a 8 années

    @François

    ok, il y a de l’inférence de type dans les generics.
    mais elle sera quand même bcp plus visible avec les closures.

    avec les generics, si l’on a un appel asList(« toto »), l’inférence T=String dans le contexte asList(T…) passe inaperçue.
    Par contre, avec « x => x + 1 », l’inférence « x est de type int » saute aux yeux (car les développeurs s’atendent à une déclaration « int x ») et de fait, l’impact de cette inférence devient bcp plus marquant pour les développeurs Java.

  9. Publié par , Il y a 8 années

    Il semble souvent y avoir un mélange entre « fonction anonyme » d’une part, et « closure » (fermeture) d’autre part. Ce ne sont pas les mêmes objets.

    Une fonction anonyme est simplement une fonction sans nom (sisi, monsieur de la Palisse). Par exemple, en Scala:

    scala> (x:Int) => x + 1
    res0: (Int) => Int =

    L’intérêt dans un langage qui les supporte et qu’on peut manipuler ces choses comme des valeurs, et donc les mettre dans une variable, les utiliser en paramètre de fonction, etc.

    Une fermeture, pour sa part, correspond à la capture de l’environnement courant d’évaluation d’une fonction pour les variables non locales à la fonction – c’est ça, « clore » les variables.

    Exemple (toujours en Scala, en attendant que Java 8 arrive): on va définir une fonction avec une variable non locale qui est close à l’appel de la fonction – le compilateur vérifie tout de même qu’une telle variable existe dans l’environnement:

    scala> def addMore(x:Int) = x + more
    :7: error: not found: value more
    def addMore(x:Int) = x + more
    ^

    scala> var more = 1
    more: Int = 1

    scala> def addMore(x:Int) = x + more
    addMore: (x: Int)Int

    scala> addMore(10)
    res3: Int = 11

    scala> more = 100
    more: Int = 100

    scala> addMore(10)
    res4: Int = 110

    Cet exemple est repris de Programming in Scala 1st edition, page 150~154, disponible ici avec de bien meilleures explications: http://www.artima.com/pins1ed/functions-and-closures.html#8.7

    Évidemment, fonctions anonymes et closures sont très fortement liées, et c’est pour cela qu’on n’arrête pas de les mélanger. Ainsi, dans l’expression qui prend une liste de nombre et la transforme en la liste de nombres successeurs suivants:

    scala> def inc(x:Int) = List(1,2,3).map(y => x + y)
    inc: (x: Int)List[Int]

    scala> inc(1)
    res5: List[Int] = List(2, 3, 4)

    On crée à chaque itération de map une closure, puisque dans ce contexte, x n’est pas local à map.

    Voilà voilà, sur ce, bonne soirée, et merci beaucoup pour l’article !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.