Getting started with lambdas : Part 1

Si vous avez suivi l’actualité Java ces derniers temps, vous avez peut-être entendu que la sortie de la “developer preview” du JDK 8 a été repoussée. Bien sûr tout le monde s’en donne à coeur joie pour vanner Oracle, c’est de bonne guerre. Mais pour une fois, ce retard est peut-être une bonne chose : toute l’équipe du JDK travaille d’arrache-pied pour peaufiner la grande nouveauté de cette version : les lambdas. Nous avons suivi avec attention leur avancée ces derniers mois, testant des JDKs, pestant contre certains choix et en admirant d’autres. Si au début je trouvais un intérêt minimal aux lambdas par rapport à ce que proposent Scala et consort, j’avoue maintenant attendre la sortie de Java 8 pour profiter des nouveautés, et écrire du code un peu plus sympa dans un langage que j’apprécie.

Voilà assez parlé de ma vie.

Ce post démarre une série qui vise à présenter les lambdas, qui sont probablement la plus grande nouveauté dans le langage depuis les génériques. Plutôt que de vous montrer directement une lambda, et nous extasier devant leur utilisation, j’aimerais vous expliquer un peu ce qui se passe derrière.

Tout repose sur le concept d’interface fonctionnelle.

Une interface vous voyez ce que c’est : un ensemble de méthodes abstraites que l’on devra implémenter dans une classe. Jusque là ça va.

public interface MessageInterface {
  public String transform(String message);
  public void print(String message);
}

Mais cette définition change avec Java 8 : une interface peut maintenant avoir des méthodes concrètes et des méthodes statiques. Oui, oui, c’est nouveau. Le mot clé `default` est utilisé pour définir une méthode concrète dans une interface. Par exemple :

public interface MessageInterfaceWithDefault {
  public String transform(String message);
  public default void print(String message) {
    System.out.println(message);
  }
}

Ca fait bizarre, mais on s’y fait. Dans le cas de cette interface, la méthode `print()` a une implémentation par défaut. Il peut également y avoir plusieurs méthodes `default` bien sûr. Ainsi si vous voulez implémenter cette interface vous avez deux solutions :

public class MessageWithoutDefault implements MessageInterfaceWithDefault {
  public String transform(String message) {
    return message;
  }
  public void print(String message) {
   System.out.println(“message :” + message);
  }
}

Cette première solution implémente l’interface en donnant une implémentation à chaque méthode, la méthode `default` étant surchargée.

Une autre solution est possible, si l’implémentation par défaut de print vous convient, vous pouvez seulement fournir une implémentation à `transform()` :

public class MessageWithDefault implements MessageInterfaceWithDefault {
  public String transform(String message) {
    return message; // oui bon d'accord ça fait rien...
  }
}

Donc en résumé, une méthode `default` dans une interface vous fournit une implémentation par défaut, libre à vous de la redéfinir ou pas.

Qu’en est il de l’héritage multiple?

Si vous héritez de deux interfaces possédant une méthode `default` avec la même signature, le compilateur va regarder si l’une des interfaces hérite de l’autre. Si c’est le cas, l’interface la plus spécialisée est préférée. Sinon vous obtenez l’erreur de compilation suivante :

class training.lambda.Both inherits unrelated defaults for print() from types training.lambda.Interface1 and training.lambda.Interface2

Voilà pour les méthodes `default`.

Revenons-en aux interfaces fonctionnelles. Lorsque vous allez écrire une lambda, vous allez en fait implémenter une interface fonctionnelle. Une interface fonctionnelle est une interface qui ne possède qu’une seule méthode abstraite. Elle peut en revanche avoir plusieurs méthodes `default`. L’interface `MessageInterfaceWithDefault` est donc une interface fonctionnelle. Vous pourriez écrire :

MessageInterfaceWithDefault asALambda = message -> “message : “ + message;

Ce code est tout à fait valide. L’expression de droite est en fait l’implémentation de la méthode `transform()` de l’interface. Ainsi si vous exécutez le code suivant :

String result = asALambda.transform(“hello”); // result = “message : hello”

On est d’accord, il n’y a pas beaucoup d’intérêt à utiliser les lambdas ainsi. Cet exemple montre seulement ce que sont réellement les lambdas. Ainsi, vous ne pouvez pas utiliser les lambdas pour implémenter des interfaces qui possèdent plusieurs méthodes abstraites : le compilateur ne saurait pas quelle méthode vous essayez d’implémenter.

Le JDK contient déjà beaucoup d’interfaces fonctionnelles : `FileFilter`, `Runnable`, `Callable`, `ActionListener`, `Comparator`…

`FileFilter` a par exemple une seule méthode abstraite : `boolean accept(File pathname)`, qui prend donc un fichier en argument et renvoie un booléen. `FileFilter` est utilisé comme argument dans la methode `listFiles()` de `File`, pour renvoyer une liste de fichiers qui correspondent au filtre. Vous voulez récupérer les répertoires d’un dossier dir, en une ligne en utilisant les lambdas ?

File[] files = dir.listFiles(f -> f.isDirectory())

`f -> f.isDirectory()` est une lambda que le compilateur interprète comme l’implémentation d’un `FileFilter`. Rapide et efficace, je ne vous montre même pas le code que l’on doit écrire actuellement.

Dans votre code, vous pourrez faire vos propres interfaces fonctionnelles, qui seront joyeusement implémentées sous forme de lambda par vos collègues, parfois même dans des modules différents. Mais que se passe-t-il si quelqu’un ajoute une méthode abstraite à votre interface? Et bien les lambdas écrites par vos collègues vont poser problème à la compilation des modules qui utilise votre interface :

incompatible types: lambda.Interface1 is not a functional interface
multiple non-overriding abstract methods found in interface com.ninja_squad.training.lambda.Interface1

Pour prévenir ce problème, une annotation @FunctionalInterface a été ajoutée. Celle-ci indique au compilateur que l’interface en question ne doit avoir qu’une et une seule méthode abstraite. Si vous essayez d’ajouter une méthode abstraite à cette interface, vous obtenez l’erreur de compilation suivante :

Unexpected @FunctionalInterface annotation
training.lambda.Interface1 is not a functional interface
multiple non-overriding abstract methods found in interface training.lambda.Interface

Autant dire que ce garde-fou devra être systématiquement utilisé si vous créez des interfaces fonctionnelles afin de prévenir de potentiels (probables?) problèmes à l’avenir. Toute personne qui modifiera l’interface saura immédiatement à quoi s’en tenir!

La prochaine fois, on regardera comment écrire une lambda en détail (enfin!).

À 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 28/02/2013, dans Getting started, et tagué , , . Bookmarquez ce permalien. 8 Commentaires.

  1. Merci pour ton post intéressant sur les lambdas.
    Il y a une erreur dans

    « public class MessageWithDefault implement MessageInterfaceWithDefault »

    (manque le s à implement).

  2. Salut et merci encore pour le post, il est vraiment tres bien ecrit. Si j’ai bien compris, ecrire une lambda c’est implementer une interface fonctionnelle. Ce n’est pas definir une fonction a la volee comme on le fait en python par exemple.

  3. Merci pour cet article instructif. Une petite suggestion cependant, j’ai dû relire environ 8 fois le passage le plus important, pour comprendre la ligne :
    MessageInterfaceWithDefault asALambda = message -> “message : “ + message;

    J’ai peut-être le cerveau particulièrement lent ce soir, mais je pense qu’un peu plus d’explications seraient les bienvenues à cet endroit. Par exemple, en ajoutant que la partie « message -> « message : » + message » remplace une sorte de fonction qui si on lui fournit le paramètre « message » retourne ( -> ) « message : » + message.

    Ca parait simple une fois qu’on l’a compris, mais ce n’est pas évident du tout en première lecture ! (mais on finit toujours par s’y faire. Je me souviens encore de mes maux de tête la première fois que j’ai lu comment fonctionnaient les génériques !)

    Allez, je me lance dans la lecture de l’article Part 2…😉

    • Hé bien, j’aurais dû lire l’article 2 avant de commenter…
      Je fais donc moi-même une suggestion en réponse à mon premier commentaire (maintenant que j’ai encore mieux compris) :

      Plutôt que d’utiliser la syntaxe la plus courte (certes assez élégante) pour un premier exemple, j’aurai plutôt utilisé quelque chose de moins déstabilisant pour le débutant, comme

      (String message) -> { return « message : » + message ; }

      en précisant ensuite qu’il était possible de simplifier cette écriture en :

      message -> « message :  » + message;

      grâce au type implicite sur la partie gauche, et au return implicite sur la partie droite.

  1. Pingback: Getting started with lambdas – Part 2 | Hype Driven Development

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 :