Getting started with lambdas – Part 2

Après une première partie dédiée à comprendre les interfaces fonctionnelles, nous allons entrer dans le vif du sujet avec cet article dont le but est de vous présenter les différentes façons d’écrire une lambda.

Supposons que nous ayons une interface Concatenator (histoire d’avoir un nom qui fait peur comme on sait faire en Java), qui prend un int et un double pour les concaténer :

interface Concatenator {
    String concat(int a, double b);                    
}

La première façon d’écrire une lambda implémentant cette interface sous forme de lambda sera :

Concatenator c = (int a, double b) -> { 
    String s = a + " " + b; 
    return s;
};

Voilà notre première lambda! Cette syntaxe est le fruit de nombreux mois de réflexion, après débat entre 3 propositions principales. On retrouve nos paramètres entre parenthèses avec leur type, suivi d’une flèche et d’un bloc de code entre accolades. Le bloc de code représente le corps de la fonction, c’est l’implémentation concrète de la méthode concat() de notre interface Concatenator. Ce corps de fonction est tout à fait classique avec un return en fin de bloc pour renvoyer le résultat.

Une autre façon, plus concise, d’écrire cette lambda est possible avec la syntaxe suivante :

Concatenator c = (int a, double b) -> return a + " " + b;

On note cette fois l’omision des accolades autour du bloc représentant la fonction. Cette syntaxe n’est bien sûr possible que si le corps de votre fonction tient sur une seule ligne, vous l’aurez compris.

Nous pouvons encore simplifier un peu l’écriture en utilisant un return implicite :

Concatenator c = (int a, double b) -> a + " " + b;

Le return a disparu ! En effet, par défaut, la valeur de l’expression à droite de la flèche est retournée par la lambda : les syntaxes avec et sans return sont donc parfaitement équivalentes.

Nous pouvons également nous reposer sur l’inférence de type et laisser le compilateur (lorsque c’est possible) déterminer les types de nos paramètres :

Concatenator c = (a, b) -> a + " " + b;

Vous voyez qu’une lambda peut être une expression très concise !

Si notre interface fonctionnelle avait une méthode avec un seul paramètre, par exemple :

interface UnaryOperator {
    int op(int a);
}   

Alors une lambda implémentant UnaryOperator pourrait être :

UnaryOperator op = (a) -> a * a;

Mais la lambda pourrait même ici se passer des parenthèses autour des paramètres :

UnaryOperator op = a -> a * a;

En revanche, une interface avec une méthode ne possédant pas de paramètres comme NumberSupplier :

interface NumberSupplier { 
   int get();
}

devra s’écrire :

NumberSupplier supplier = () -> 25;

Enfin, lorsque la lambda est un appel à une fonction de l’objet passé en paramètre, il est possible d’utiliser une syntaxe légérement différente. Ainsi, pour une interface fonctionnelle :

interface StringToIntFunction {                        
    int toInt(String s);
}

qui transforme une chaîne de caractères en entier, on peut écrire une lambda comme suit :

StringToIntFunction f = s -> s.length();

ou encore écrire :

StringToIntFunction f = String::length;

Ce :: est un nouvel opérateur Java : il agit comme un appel à la méthode length de l’argument de la méthode toInt. Un peu surprenant au début, mais on s’habitue vite. Cet opérateur peut également s’appliquer à un constructeur. Notre méthodetoIntpourrait donc également s’écrire :

StringToIntFunction f = Integer::new;

ce qui équivaut à

StringToIntFunction f = s -> new Integer(s);

L’opérateur peut aussi s’appliquer à une méthode statique, comme parseInt pour la classe Integer. La lambda :

StringToIntFunction f = s -> Integer.parseInt(s);

est donc identique à :

StringToIntFunction f = Integer::parseInt;

Enfin, il est aussi possible de faire référence à une méthode d’un autre objet. Si nous supposons l’existence d’une HashMapstringToIntMap dans notre code, nous pouvons écrire une lambda comme suit :

StringToIntFunction f = stringToIntMap::get;

qui signifie la même chose que :

StringToIntFunction f = s -> stringToIntMap.get(s);

Voilà, nous avons fait un inventaire exhaustif des façons d’écrire une lambda. La possibilité d’omettre les parenthèses, types, accolades et le mot clé return est appréciable et donne une syntaxe très peu verbeuse. L’ajout de l’opérateur :: introduit de nouvelles possibilités dans l’écriture comme vous avez pu le constater. Son utilisation demande un peu de pratique, mais cela viendra vite naturel !

La prochaine fois nous regarderons une petite subtilité sur la portée des variables utilisables dans une lambda. Teasing teasing…

À propos de Cédric Exbrayat

Cédric Exbrayat, développeur et fondateur Ninja Squad, se réunit avec ses semblables régulièrement que ce soit au Lyon JUG ou à Mix-it, dont il est le fondateur. Java et JS for food and fun.

Publié le 05/04/2013, dans Getting started, et tagué , , . Bookmarquez ce permalien. 2 Commentaires.

  1. Lorsque tu dis « Nous pouvons également nous reposer sur l’inférence de type et laisser le compilateur (lorsque c’est possible) déterminer les types de nos paramètres », est-ce un abus de langage ou y a-t-il réellement une inférence de type qui se passe ?

    Car pour moi, le compilateur n’a pas beaucoup à travailler : une interface fonctionnelle n’a de toutes façons qu’une seule méthode, donc il n’a juste qu’à vérifier si a et b sont bien des instances des types attendus.

    Je rate quelque chose ?

    • Je vais citer Goetz pour te répondre « Since a functional interface target type already « knows » what types the lambda expression’s formal parameters should have, it is often unnecessary to repeat them. The use of target typing often allows the lambda parameters’ types to be inferred ». Dans la plupart des cas, pas besoin de typer les arguments (encore que sur la ML des lambdas, il a été évoqué à plusieurs reprises des corner cases avec les génériques, mais je crois que c’est en passe d’être, ou déjà, résolu).

      Bien sûr cela ne t’empêchera pas de devoir le typer si tu veux appeler une méthode particulière dessus :
      Collectors.partitioningBy((Tweet tweet) -> tweet.containsHashTag(« #lambda »)) // OK
      Collectors.partitioningBy(tweet -> tweet.containsHashTag(« #lambda »)) // compile error (cannot resolve containsHashTag)

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :