IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

12. Les expressions lambda

 

chapitre    1 2

 

Java est un langage orienté objet : à l'exception des instructions et des données primitives, tout le reste est objets, même les tableaux et les chaînes de caractères.

Java ne propose pas la possibilité de définir une fonction/méthode en dehors d'une classe ni de passer une telle fonction en paramètre d'une méthode. Depuis Java 1.1, la solution pour passer des traitements en paramètres d'une méthode est d'utiliser les classes anonymes internes.

Pour faciliter, entre autres, cette mise à oeuvre, Java 8 propose les expressions lambda. Les expressions lambda sont aussi nommées closures ou fonctions anonymes : leur but principal est de permettre de passer en paramètre un ensemble de traitements.

De plus, la programmation fonctionnelle est devenue prédominante dans les langages récents. Dans ce mode de programmation, le résultat de traitements est décrit mais pas la façon dont ils sont réalisés. Ceci permet de réduire la quantité de code à écrire pour obtenir le même résultat.

Par exemple, pour afficher les éléments d'une liste

Exemple :
for (int i = 0; i < list.size(); i++) {
  System.out.println(list.get(i));
}

Ce code est composé de deux traitements : une boucle pour itérer sur chaque élément et l'affichage d'un élément dans l'itération. Le code de la boucle pourrait être factorisé dans une méthode qui attendrait en paramètre une fonction qui contenant le traitement à réaliser sur chaque élément.

Exemple ( code Java 8 ) :
list.forEach(System.out::println);

La mise en oeuvre de cette fonctionnalité requiert deux fonctionnalités de Java 8 :

  • les méthodes par défaut pour définir la méthode foreach() dans l'interface Iterable qui attend en paramètre une interface fonctionnelle de type Consumer
  • les expressions lambda pour permettre de passer en paramètre une fonction, sous la forme d'une référence de méthode dans l'exemple

Les expressions lambda sont donc une des plus importantes nouveautés de Java 8 voire même la plus importante évolution apportée au langage Java depuis sa création. C'est donc logiquement la fonctionnalité la plus médiatique de Java 8.

Les spécifications des expressions lambda sont définies dans la JSR 335 (Project Lambda).

Ce chapitre contient plusieurs sections :

 

12.1. L'historique des lambdas pour Java

L'ajout des expressions lambda dans le langage Java a été un processus long qui a nécessité plus de huit années de travail.

Les premières propositions datent de 2006. En 2006-2007, plusieurs propositions de spécifications s'opposent :

  • BGGA : proposée par Gilad Bracha, Neal Gafter, James Gosling et Peter von der Ahé. Elle utilise une syntaxe avec l'opérateur =>. Elle définit les functions type, l'annotation @Shared pour pouvoir modifier une variable du contexte, les methods references avec l'opérateur #
  • CICE : proposée par Bob Lee, Doug Lea et Josh Bloch. Elle utilise une écriture simplifiée des classes anonymes internes
  • FCM : proposée par Stephen Colebourne et Stefan Schulz. Elle est plus simple que BGGA et plus complète que CICE. La définition de method types se fait avec l'opérateur #. Elle permet la modification des variables du contexte englobant

Aucune de ces propositions ne sera intégralement retenue mais les meilleures idées sont utilisées pour spécifier les expressions lambda :

  • ne pas ajouter un nouveau type fonction au langage pour éviter les écueils des generics
  • s'appuyer sur les interfaces qui existent déjà et permettent donc une transition en douceur
  • les expressions lambda ne sont pas transformées en classes par le compilateur : elles n'utilisent donc pas les classes anonymes internes

Lors de la conception des expressions lambda, le choix a été fait de ne pas ajouter un type spécial dans le langage, ce qui limite les fonctionnalités d'une expression lambda par rapport à leur équivalent dans d'autres langages mais réduit les impacts dans le langage Java. Par exemple, il n'est pas possible d'assigner une expression lambda à une variable de type Object parce que le type Object n'est pas une interface fonctionnelle.

Ce n'est qu'en 2014, avec Java 8, que les expressions lambda qui permettent la mise en oeuvre d'une forme de closures sont intégrées dans le langage Java. Avant Java 8, la seule solution était d'utiliser une classe anonyme interne.

Exemple :
monBouton.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent event) {
    System.out.println("clic");
  }
});

Dans ce cas, une nouvelle instance du type de l'interface est passée en paramètre. Avec Java 8, il est possible d'utiliser une expression lambda.

Exemple ( code Java 8 ) :
monBouton.addActionListener(event -> System.out.println("clic"));

Dans ce cas, c'est une expression lambda qui est passée en paramètre. Elle permet de définir une implémentation d'une interface fonctionnelle sous la forme d'une expression composée d'une liste de paramètres et d'un corps qui peut être une simple expression ou un bloc de code.

Une expression lambda est utilisée pour représenter une interface fonctionnelle sous la forme d'une expression de la forme :

(arguments) -> corps

L'opérateur -> sépare le ou les paramètres du bloc de code qui va les utiliser. Le type du paramètre n'est pas obligatoire : le compilateur va tenter de réaliser une inférence du type pour le déterminer selon le contexte. Dans l'exemple ci-dessus, le compilateur va déterminer que le paramètre de l'interface ActionListener est de type ActionEvent.

Une expression lambda est typée de manière statique. Ce type doit être une interface fonctionnelle.

Les exemples ci-dessous illustrent leur utilisation pour définir un thread. Avant Java 8, pour définir un thread, il était possible de créer une classe anonyme de type Runnable et de passer son instance en paramètre du constructeur d'un thread.

Exemple :
Thread monThread = new Thread(new Runnable() {
  @Override
  public void run(){
    System.out.println("Mon traitement ");
  }
});
monThread.start();

A partir de Java 8, il est possible d'utiliser une expression lambda en remplacement de la classe anonyme pour obtenir le même résultat avec une syntaxe plus concise.

Exemple :
Thread monThread = new Thread(() -> { System.out.println("Mon traitement"); });
monThread.start();

L'utilisation d'une expression lambda évite d'avoir à écrire le code nécessaire à la déclaration de la classe anonyme et de la méthode.

Associées à d'autres fonctionnalités du langage (méthode par défaut, ...) et de l'API (Stream), les lambdas modifient profondément la façon dont certaines fonctionnalités sont codées en Java. Cet impact est plus important que certaines fonctionnalités des versions précédentes de Java comme les generics ou les annotations.

 

12.2. Les expressions lambda

Les expressions lambda permettent d'écrire du code plus concis, donc plus rapide à écrire, à relire et à maintenir. C'est aussi un élément important dans l'introduction de la programmation fonctionnelle dans le langage Java qui était jusqu'à la version 8 uniquement orienté objet.

Une expression lambda est une fonction anonyme : sa définition se fait sans déclaration explicite du type de retour, ni de modificateurs d'accès ni de nom. C'est un raccourci syntaxique qui permet de définir une méthode directement à l'endroit où elle est utilisée.

Une expression lambda est donc un raccourci syntaxique qui simplifie l'écriture de traitements passés en paramètre. Elle est particulièrement utile notamment lorsque le traitement n'est utile qu'une seule fois : elle évite d'avoir à écrire une méthode dans une classe.

Une expression lambda permet d'encapsuler un traitement pour être passé à d'autres traitements. C'est un raccourci syntaxique aux classes anonymes internes pour une interface qui ne possède qu'une seule méthode abstraite. Ce type d'interface est nommé interface fonctionnelle.

Lorsque l'expression lambda est évaluée par le compilateur, celui-ci infère le type vers l'interface fonctionnelle. Cela lui permet d'obtenir des informations sur les paramètres utilisés, le type de la valeur de retour, les exceptions qui peuvent être levées.

Elles permettent d'écrire du code plus compact et plus lisible. Elles ne réduisent pas l'aspect orienté objet du langage qui a toujours été une force mais au contraire, rendent celui-ci plus riche et plus élégant pour certaines fonctionnalités.

 

12.2.1. La syntaxe d'une expression lambda

Un des avantages des expressions lambda est d'avoir une syntaxe très simple.

La syntaxe d'une expression lambda est composée de trois parties :

  • un ensemble de paramètres, d'aucun à plusieurs
  • l'opérateur ->
  • le corps de la fonction

Elle peut prendre deux formes principales :

(paramètres) -> expression;

(paramètres) -> { traitements; }

L'écriture d'une expression lambda doit respecter plusieurs règles générales :

  • zéro, un ou plusieurs paramètres dont le type peut être déclaré explicitement ou inféré par le compilateur selon le contexte
  • les paramètres sont entourés par des parenthèses et séparés par des virgules. Des parenthèses vides indiquent qu'il n'y a pas de paramètre
  • lorsqu'il n'y a qu'un seul paramètre et que son type est inféré alors les parenthèses ne sont pas obligatoires
  • le corps de l'expression peut contenir zéro, une ou plusieurs instructions. Si le corps ne contient d'une seule instruction, les accolades ne sont pas obligatoires et le type de retour correspond à celui de l'instruction. Lorsqu'il y a plusieurs instructions alors elles doivent être entourées avec des accolades

 

12.2.1.1. Les paramètres d'une expression lambda

Dans la définition d'une expression lambda, l'opérateur -> permet de séparer les paramètres des traitements qui les utiliseront. Les paramètres de l'expression doivent donc être déclarés à gauche de l'opérateur ->.

Les paramètres d'une expression lambda doivent correspondre à ceux définis dans l'interface fonctionnelle.

Les paramètres de l'expression doivent respecter certaines règles :

  • une expression peut n'avoir aucun, un seul ou plusieurs paramètres
  • le type des paramètres peuvent être explicitement déclaré ou être inféré par le compilateur selon le contexte dans lequel l'expression est utilisée
  • les paramètres sont entourés de parenthèses, chacun étant séparé par une virgule
  • des parenthèses vides indiquent qu'il n'y a pas de paramètre
  • s'il n'y a qu'un seul paramètre dont le type n'est pas explicitement précisé, alors l'utilisation des parenthèses n'est pas obligatoire

Une expression lambda peut ne pas avoir de paramètre.

Exemple ( code Java 8 ) :
Runnable monTraitement = () -> System.out.println("traitement");

Pour préciser qu'il n'y a aucun paramètre, il faut utiliser une paire de parenthèses vide. Dans ce cas, la méthode de l'interface fonctionnelle ne doit pas avoir de paramètre.

Si l'expression lambda ne possède qu'un seul paramètre alors il y a deux syntaxes possibles. La plus standard est d'indiquer le paramètre entre deux parenthèses.

Exemple ( code Java 8 ) :
Consumer<String> afficher = (param) -> System.out.println(param);

Il est aussi possible d'omettre les parenthèses uniquement si le type peut être inféré.

Exemple ( code Java 8 ) :
Consumer<String> afficher = param -> System.out.println(param);

Il n'est pas possible d'omettre les parenthèses si le type est précisé explicitement.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Consumer;

public class TestLambda {

  public static void main(String[] args) {
    Consumer<String> afficher = String param -> System.out.println(param);
  }
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:10: error: ';' expected
    Consumer<String> afficher = String param -> System.out.println(param);
                                      ^
com\jmdoudoux\dej\java8\lambda\TestLambda.java:10: error: not a statement
    Consumer<String> afficher = String param -> System.out.println(param);
                                       ^
2 errors

Si l'expression lambda possède plusieurs paramètres alors ils doivent être entourés obligatoirement par des parenthèses et séparés les uns des autres par une virgule.

Exemple ( code Java 8 ) :
    BiFunction<Integer, Integer, Long> additionner = (val1, val2) -> (long) val1 + val2;

Il est possible de déclarer explicitement le type du ou des paramètres de l'expression lambda.

Exemple ( code Java 8 ) :
Consumer<String> afficher = (String param) -> System.out.println(param);

Généralement, le type d'un paramètre n'est pas obligatoire si le compilateur est capable de l'inférer : dans la plupart des cas, le compilateur est capable de déterminer le type du paramètre à partir de celui correspondant à l'interface fonctionnelle. Si ce n'est pas le cas, le compilateur génère une erreur et il est alors nécessaire de préciser ce type explicitement.

Dans l'exemple ci-dessus, le fait que l'interface fonctionnelle Consumer soit typée avec un generic String permet au compilateur de savoir que le type du paramètre est String. L'exemple ci-dessus peut alors être écrit sans préciser le type du paramètre.

Exemple ( code Java 8 ) :
Consumer<String> afficher = (param) -> System.out.println(param);

Le type précisé pour un paramètre doit correspondre à celui défini dans le type l'interface fonctionnelle.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Consumer;

public class TestLambda {

  public static void main(String[] args) {

    Consumer afficher = (String param) -> System.out.println(param);
  }
}

L'exemple ci-dessus génère une erreur à la compilation.

Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:16: error: incompatible types: in
compatible parameter types in lambda expression
    Consumer afficher = (String param) -> System.out.println(param);
                        ^
1 error

Il n'est cependant pas possible de mixer dans la même expression des paramètres déclarés explicitement et inférés.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Comparator;

public class TestLambda {

  public static void main(String[] args) {

    Comparator<String> comparator = 
      (chaine1, String chaine2) -> Integer.compare(chaine1.length(),chaine2.length());
  }
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:10: error: <identifier> expected
      (chaine1, String chaine2) -> Integer.compare(chaine1.length(),chaine2.leng
th());
              ^
1 error

Il est possible d'utiliser le modificateur final sur les paramètres si leur type est précisé explicitement.

Exemple ( code Java 8 ) :
 Comparator<String> comparator = (final String chaine1, final String chaine2) 
   -> Integer.compare(chaine1.length(),chaine2.length());

Ce n'est pas possible si le type est inféré par le compilateur sinon il génère une erreur.

Exemple ( code Java 8 ) :
 Comparator<String> comparator = (final chaine1, final chaine2) 
      -> Integer.compare(chaine1.length(),chaine2.length());
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:12: error: <identifier> expected
    Comparator<String> comparator = (final chaine1, final chaine2) -> Integer.co
mpare(chaine1.length(),chaine2.length());
                                                  ^
com\jmdoudoux\dej\java8\lambda\TestLambda.java:12: error: <identifier> expected
    Comparator<String> comparator = (final chaine1, final chaine2) -> Integer.co
mpare(chaine1.length(),chaine2.length());
                                                                 ^
2 errors

Il est aussi possible d'annoter les paramètres d'une expression lambda uniquement si leur type est déclaré explicitement.

Exemple ( code Java 8 ) :
    Comparator<String> comparator = (@NotNull String chaine1, @NotNull String chaine2) 
      -> Integer.compare(chaine1.length(),chaine2.length());

 

12.2.1.2. Le corps d'une expression lambda

Le corps d'une expression lambda est défini à droite de l'opérateur ->. Il peut être :

  • une expression unique
  • un bloc de code composé d'une ou plusieurs instructions entourées par des accolades

Le corps de l'expression doit respecter certaines règles :

  • il peut n'avoir aucune, une seule ou plusieurs instructions
  • lorsqu'il ne contient qu'une seule instruction, les accolades ne sont pas obligatoires et la valeur de retour est celle de l'instruction si elle en possède une
  • lorsqu'il y a plusieurs instructions, elles doivent obligatoirement être entourées d'accolades
  • la valeur de retour est celle de la dernière expression ou void si rien n'est retourné

Si le corps est simplement une expression, celle-ci est évaluée et le résultat de cette évaluation est renvoyé s'il y en a un.

Exemple ( code Java 8 ) :
    BiFunction<Integer, Integer, Long> additionner = (val1, val2) -> (long) val1 + val2;

Il n'est jamais nécessaire de préciser explicitement le type de retour : le compilateur doit être en mesure de le déterminer selon le contexte. Si ce n'est pas le cas, le compilateur émet une erreur.

Si l'expression ne produit pas de résultat, celle-ci est simplement exécutée. Il n'est pas nécessaire d'entourer d'accolades si le corps de l'expression invoque une méthode dont la valeur de retour est void.

Exemple ( code Java 8 ) :
  Consumer<String> afficher = (String param) -> System.out.println(param);

Les traitements d'une expression lambda peuvent contenir plusieurs opérations qui doivent être regroupées dans un bloc de code entouré d'accolades comme pour le corps d'une méthode.

Exemple ( code Java 8 ) :
Runnable monTraitement = () -> {
  System.out.println("traitement 1");
  System.out.println("traitement 2");};

Le bloc de code est évalué comme si c'était celui d'une méthode : il est possible de terminer son exécution en utilisant l'instruction return ou en levant une exception.

Si le bloc de code doit retourner une valeur, il faut utiliser le mot clé return.

Exemple ( code Java 8 ) :
    Function<Integer, Boolean> isPositif = valeur -> {
            return valeur >= 0;
    };

Important : toutes les branches du code du corps de l'expression doivent obligatoirement retourner une valeur ou lever une exception si au moins une branche retourne une valeur. Si ce n'est pas le cas, le compilateur émet une erreur.

Exemple ( code Java 8 ) :
    Function<Integer, Boolean> isPositif = valeur -> {
          if (valeur >= 0) {
              return true;
          }
    };

L'exemple ci-dessus ne se compile pas car lorsque la valeur est négative, il n'y a pas de valeur de retour définie.

Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:9: error: incompatible types: bad
 return type in lambda expression
    Function<Integer, Boolean> isPositif = valeur -> {
                                         ^
    missing return value
1 error

Les mots clés break et continue ne peuvent pas être utilisés comme instructions de premier niveau dans le bloc de code mais ils peuvent être utilisés dans des boucles.

 

12.2.2. Des exemples d'expressions lambda

Cette section fournit quelques exemples d'expressions lambda.

Exemple

Description

() -> 123

() -> { return 123 };

N'accepter aucun paramètre et renvoyer la valeur 123

x -> x * 2

Accepter un nombre et renvoyer son double

(x, y) -> x + y

Accepter deux nombres et renvoyer leur somme

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

Accepter deux entiers et renvoyer leur somme

(String s) -> System.out.print(s)

Accepter une chaîne de caractères et l'afficher sur la sortie standard sans rien renvoyer

c -> { int s = c.size(); c.clear(); return s; }

Renvoyer la taille d'une collection après avoir effacé tous ses éléments.

Cela fonctionne aussi avec un objet qui possède une méthode size() renvoyant un entier et une méthode clear()

n -> n % 2 != 0;

Renvoyer un booléen qui précise si la valeur numérique est impaire

(char c) -> c == 'z';

Renvoyer un booléen qui précise si le caractère est 'z'

() -> { System.out.println("Hello World"); };

Afficher "Hello World" sur la sortie standard

(val1, val2) -> { return val1 >= val2; }

(val1, val2) -> val1 >= val2;

Renvoyer un booléen qui précise si la première valeur est supérieure ou égale à la seconde

() -> { for (int i = 0; i < 10; i++) traiter(); }

Exécuter la méthode traiter() dix fois

p -> p.getSexe() == Sexe.HOMME
&& p.getAge() >= 7
&& p.getAge() <= 77

Renvoyer un booléen pour un objet possédant une méthode getSexe() et getAge() qui vaut true si le sexe est masculin et l'age compris entre 7 et 77 ans

 

12.2.3. La portée des variables

Vis à vis de la portée et de la visibilité des variables, une expression lambda se comporte syntaxiquement comme un bloc de code imbriqué.

Comme pour les classes anonymes internes, une expression lambda peut avoir accès à certaines variables définies dans le contexte englobant.

Dans le corps d'une expression lambda, il est donc possible d'utiliser :

  • les variables passées en paramètre de l'expression
  • les variables définies dans le corps de l'expression
  • les variables final définies dans le contexte englobant
  • les variables effectivement final définies dans le contexte englobant : ces variables ne sont pas déclarées final mais une valeur leur est assignée et celle-ci n'est jamais modifiée. Il serait donc possible de les déclarer final sans que cela n'engendre de problème de compilation. Le concept de variables effectivement final a été introduit dans Java 8

Une expression lambda peut avoir accès aux variables définies dans son contexte englobant.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
public class TestPorteeLambda {

  public static void main(String[] args) {
    afficher("Bonjour",5);
  }

  public static void afficher(String message, int repetition) {
    Runnable r = () -> {
      for (int i = 0; i < repetition; i++) {
        System.out.println(message);
      }
    };
    new Thread(r).start();
  }
}

Dans l'exemple ci-dessus, les variables message et repetition ne sont pas définies dans l'expression lambda mais sont des paramètres de la méthode englobante qui sont accédées dans l'expression lambda. Ces variables ne sont pas déclarées final mais elles sont effectivement final donc elles sont utilisables dans l'expression lambda.

L'accès aux variables du contexte englobant est limité par une contrainte forte : seules les variables dont la valeur ne change pas peuvent être accédées.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import javax.swing.JButton;

public class TestPorteeLambda {

  public static void main(String[] args) {
    // ...

    JButton bouton = new JButton("Incrementer");

    int compteur = 0;
    bouton.addActionListener(event -> compteur++);
    // ...

  }
}
Résultat :
C:\java\TestJava8\src>javac com/jmdoudoux/dej/java8/lambda/TestPorteeLambda.java
com\jmdoudoux\dej\java8\lambda\TestPorteeLambda.java:12: error: local variables
referenced from a lambda expression must be final or effectively final
    bouton.addActionListener(event -> compteur++);
                                      ^
1 error

La même erreur de compilation est obtenue si la modification est faite dans le contexte englobant de l'expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import javax.swing.JButton;

public class TestPorteeLambda {

  public static void main(String[] args) {
    JButton bouton = new JButton("Incrementer");

    int compteur = 0;
    compteur++;
    bouton.addActionListener(event -> System.out.println(compteur));
  }
}

Il est possible de passer en paramètre un objet mutable et de modifier l'état de cet objet. Le compilateur vérifie simplement que la référence que contient la variable ne change pas. Comme tout en Java, il est de la responsabilité du développeur de gérer les éventuels accès concurrents lors de ces modifications.

Comme avec les classes anonymes internes, il est aussi possible de définir un tableau d'un seul élément de type int et d'incrémenter la valeur de cet élément.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import javax.swing.JButton;

public class TestPorteeLambda {

  public static void main(String[] args) {
    JButton bouton = new JButton("Incrementer");

    int[] compteur = new int[1];
    bouton.addActionListener(event -> compteur[0]++);
  }
}

L'utilisation de cette solution de contournement n'est pas recommandée d'autant qu'elle n'est pas thread-safe. Il est préférable d'utiliser une variable de type AtomicXXX pour le compteur.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JButton;

public class TestPorteeLambda {

  public static void main(String[] args) {
    JButton bouton = new JButton("Incrementer");

    AtomicInteger compteur = new AtomicInteger(0);
    bouton.addActionListener(event -> compteur.incrementAndGet());
  }
}

Le corps de l'expression lambda est soumis aux mêmes règles de gestion de la portée des variables qu'un bloc de code ordinaire.

Les variables locales et les paramètres déclarés dans une expression lambda doivent l'être comme s'ils l'étaient dans le contexte englobant : une expression lambda ne définit pas de nouvelle portée. Ainsi, il n'est pas possible de définir deux variables avec le même nom dans un même bloc de code, donc il n'est pas possible de définir une variable dans l'expression lambda si celle-ci est déjà définie dans le contexte englobant.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestComparator {

  public static void main(String[] args) {
    Personne p1 = new Personne("nom3","prenom3");
    Personne p2 = new Personne("nom1","prenom1");
    Personne p3 = new Personne("nom2","prenom2");
    List<Personne> personnes = new ArrayList(3);
    personnes.add(p1);
    personnes.add(p2);
    personnes.add(p3);

    Comparator<Personne> triParNom = (Personne p1,  Personne p2) -> {
      return p2.getNom().compareTo(p1.getNom());
    };
  }
}
Résultat :
C:\java\TestJava8\src>javac com/jmdoudoux/dej/java8/lambda/TestComparator.java
com\jmdoudoux\dej\java8\lambda\TestComparator.java:20: error: variable p1 is alr
eady defined in method main(String[])
    Comparator<Personne> triParNom = (Personne p1,  Personne p2) -> {
                                               ^
com\jmdoudoux\dej\java8\lambda\TestComparator.java:20: error: variable p2 is alr
eady defined in method main(String[])
    Comparator<Personne> triParNom = (Personne p1,  Personne p2) -> {
                                                             ^
2 errors

Le mot clé this fait référence à l'instance courante dans le bloc de code englobant et dans l'expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
public class TestPorteeLambda {

  public static void main(String[] args) {
    TestPorteeLambda instance = new TestPorteeLambda();
    System.out.println(instance.toString());
    instance.executer();    
  }

  public void executer() {
    Runnable runnable = () -> { System.out.println(this.toString());};
    Thread thread = new Thread(runnable);
    thread.start();
  }
}
Résultat :
fr.jmdoudoux.dej.java8.lambda.TestPorteeLambda@94b612
fr.jmdoudoux.dej.java8.lambda.TestPorteeLambda@94b612

Le mot clé super fait référence à l'instance de la classe mère de this.

 

12.2.4. L'utilisation d'une expression lambda

Une expression lambda ne peut être utilisée que dans un contexte où le compilateur peut identifier l'utilisation de son type cible (target type) qui doit être une interface fonctionnelle :

  • déclaration d'une variable
  • assignation d'une variable
  • valeur de retour avec l'instruction return
  • initialisation d'un tableau
  • paramètre d'une méthode ou d'un constructeur
  • corps d'une expression lambda
  • opérateur ternaire ?:
  • cast

Par exemple, la déclaration d'une expression lambda peut être utilisée directement en paramètre d'une méthode.

Exemple ( code Java 8 ) :
monBouton.addActionListener(event -> System.out.println("clic"));

Il est aussi possible de définir une variable ayant pour type une interface fonctionnelle qui sera initialisée avec l'expression lambda.

Exemple ( code Java 8 ) :
ActionListener monAction = event -> System.out.println("clic");
      
monBouton.addActionListener(monAction);

Une expression lambda est définie grâce à une interface fonctionnelle : une instance d'une expression lambda qui implémente cette interface est un objet. Cela permet à une expression lambda :

  • de s'intégrer naturellement dans le système de type de Java
  • d'hériter des méthodes de la classe Object
Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

public class TestLambda {

  public static void main(String[] args) {
    Runnable monTraitement = () -> {
      System.out.println("mon traitement");
    };
    Object obj = monTraitement;
  }
}

Attention cependant, une expression lambda ne possède pas forcément d'identité unique : la sémantique de la méthode equals() n'est donc pas garantie.

Le type dans la déclaration, désigné par target type, est le type de l'interface fonctionnelle dans le contexte auquel l'expression lambda sera utilisée.

Une expression lambda sera transformée par le compilateur en une instance du type de son interface fonctionnelle selon le contexte dans lequel elle est définie :

  • soit celle du type à laquelle l'expression est assignée
  • soit celle du type du paramètre à laquelle l'expression est passée

L'expression lambda ne contient pas elle-même d'information sur l'interface fonctionnelle qu'elle implémente. Cette interface sera déduite par le compilateur en fonction de son contexte d'utilisation.

Le type de l'interface fonctionnelle est déterminé par le compilateur en fonction du contexte de son utilisation. Deux expressions lambda syntaxiquement identiques peuvent donc être compatibles avec plusieurs interfaces fonctionnelles et peuvent donc être compilées en deux objets de type différents. Une même expression lambda peut donc être assignée à plusieurs interfaces fonctionnelles tant qu'elle respecte leur contrat.

Exemple ( code Java 8 ) :
    LongFunction longFunction = x -> x * 2;
    IntFunction  intFunction  = x -> x * 2;
    Callable<String> monCallable = () -> "Mon traitement";
    Supplier<String> monSupplier = () -> "Mon traitement"; 

Le compilateur associe une expression lambda à une interface fonctionnelle et comme une interface fonctionnelle ne peut avoir qu'une seule méthode abstraite :

  • les types des paramètres doivent correspondre à ceux des paramètres de la méthode
  • le type de retour du corps de l'expression doit correspondre à celui de la méthode
  • toutes les exceptions levées dans le corps de l'expression doivent être compatibles avec les exceptions déclarées dans la clause throws de la méthode
Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
public class Calculatrice {

  @FunctionalInterface
  interface OperationEntiere {
    long effectuer(int a, int b);
  }

  public long calculer(int a, int b, OperationEntiere operation) {
    return operation.effectuer(a, b);
  }

  public static void main(String[] args) {
    Calculatrice calc = new Calculatrice();
    OperationEntiere addition = (a, b) -> a + b;
    OperationEntiere soustraction = (a, b) -> a - b;

    System.out.println(calc.calculer(10, 5, addition));
    System.out.println(calc.calculer(10, 5, soustraction));
  }
}
Résultat :
15
5

Il est possible d'imbriquer des expressions lambda mais dans ce cas le code devient moins lisible.

 

12.3. Les références de méthodes

Les références de méthodes permettent d'offrir une syntaxe simplifiée pour invoquer une méthode comme une expression lambda : elles offrent un raccourci syntaxique pour créer une expression lambda dont le but est d'invoquer une méthode ou un constructeur.

Une expression lambda correspond à une méthode anonyme dont le type est défini par une interface fonctionnelle. Les références de méthodes ont un rôle similaire mais au lieu de fournir une implémentation, une référence de méthode permet d'invoquer une méthode statique ou non ou un constructeur.

Les références de méthodes ou de constructeurs utilisent le nouvel opérateur ::.

Il existe quatre types de références de méthodes :

Type

Syntaxe

Exemple

Référence à une méthode statique

nomClasse::nomMethodeStatique

String::valueOf

Référence à une méthode sur une instance

objet::nomMethode

personne::toString

Référence à une méthode d'un objet arbitraire d'un type donné

nomClasse::nomMethode

Object::toString

Référence à un constructeur

nomClasse::new

Personne::new


Il y a deux possibilités pour invoquer une méthode d'une instance :

  • préciser directement l'instance concernée dans la référence de méthodes
  • préciser le type concernée dans la référence de méthodes : dans ce cas, l'instance sera passée en paramètre pour désigner celle qui sera invoquée

La syntaxe d'une référence de méthode est composée de trois éléments :

  • un qualificateur qui précise le nom d'une classe ou d'une instance sur lequel la méthode sera invoquée
  • l'opérateur ::
  • un identifiant qui précise le nom de la méthode ou l'opérateur new pour désigner un constructeur

Le qualificateur peut être :

  • un type pour les méthodes statiques et les constructeurs
  • un type ou une expression pour les méthodes non statiques. L'expression doit préciser l'objet sur lequel la méthode est invoquée

Une référence de constructeur comprend :

  • un qualificateur qui est un type dont il est possible de créer une instance : cela exclut les interfaces et les classes abstraites
  • l'opérateur ::
  • le mot clé new

Il n'est pas rare que le corps d'une expression lambda se résume à invoquer une méthode qui contient les traitements à exécuter. Cette technique est d'ailleurs utile pour déboguer le contenu de l'expression lambda car il est plus facile de mettre un point d'arrêt dans le corps d'une méthode que dans une expression lambda.

Exemple :
monButton.setOnAction(event -> System.out.println(event));

Comme le corps de l'expression lambda ne contient que l'invocation d'une méthode, il est possible de remplacer l'expression lambda par une référence de méthode.

Exemple :
monButton.setOnAction(System.out::println);

Le tableau ci-dessous donne quelques exemples de références de méthodes et leurs expressions lambda équivalentes :

Type

Référence de méthode

Expression lambda

Référence à une méthode statique

System.out::println

Math::pow

x -> System.out.println(x)

(x, y) -> Math.pow(x, y)

Référence à une méthode sur une instance

monObject::maMethode

x -> monObject.maMethode(x)

Référence à une méthode d'un objet arbitraire d'un type donné

String::compareToIgnoreCase

(x, y) -> x.compareToIgnoreCase(y)

Référence à un constructeur

MaClasse::new

() -> new MaClasse();


Dans le cas d'une référence à une méthode statique ou d'une référence à une méthode non statique sur une instance, les paramètres définis dans l'interface fonctionnelle sont simplement passés en paramètres de la méthode invoquée.

Dans le cas d'une référence à une méthode non statique sur une classe, le premier paramètre est l'instance sur laquelle la méthode sera invoquée avec les autres paramètres qui lui sont passés.

La méthode ou le constructeur qui sera invoqué est déterminé par le compilateur selon le contexte d'utilisation. Une méthode ou un constructeur peut avoir plusieurs surcharges. Le compilateur s'appuie sur l'interface fonctionnelle utilisée dans le contexte : la surcharge invoquée sera celle dont les paramètres correspondent à ceux définis dans l'unique méthode abstraite de l'interface fonctionnelle.

 

12.3.1. La référence à une méthode statique

La référence à une méthode statique permet d'invoquer une méthode statique d'une classe.

La syntaxe d'utilisation est :

nom_de_la_classe::nom_de_la_methode_statique

L'exemple ci-dessous invoque la méthode statique de trois manières : la version historique en utilisant une classe anonyme interne et les deux possibilités offertes par Java 8 c'est à dire une expression lambda et une référence de méthode. Les trois invocations sont rigoureusement identiques, seule la quantité de code nécessaire pour les réaliser change.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;

public class TestMethodeReference {

  public static void main(String[] args) {
   
    // classe anonyme interne

    new Thread(new Runnable() {
      @Override
      public void run() {
        executer();
      }
    }).start();
    
    // expression lambda

    new Thread(() -> executer()).start();

    // référence de méthode statique

    new Thread(TestMethodeReference::executer).start();
  }

  static void executer() {
    System.out.println("execution de mon traitement par "+Thread.currentThread().getName());
  }
}
Résultat :
execution de mon traitement par Thread-0
execution de mon traitement par Thread-1
execution de mon traitement par Thread-2

L'exemple ci-dessous définit une interface fonctionnelle qui est utilisée en paramètre d'une méthode.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Arrays;
import java.util.List;

public class TestMethodeReference {

  public static void main(String[] args) {
    List<String> fruits = Arrays.asList("melon", "abricot", "fraise", "cerise");
    afficher(fruits, (fmt, arg) -> String.format(fmt, arg));
  }

  public static void afficher(List<String> liste, MonFormateur formateur) {
    int i = 0;
    for (String element : liste) {
      i++;
      System.out.print(formateur.formater("%3d %s%n", i, element));
    }
  }
}

@FunctionalInterface
interface MonFormateur {
  String formater(String format, Object... arguments);
}
Résultat :
  1 melon
  2 abricot
  3 fraise
  4 cerise

A la place de l'expression lambda, il est possible d'utiliser directement une référence de méthodes static sur format() de la classe String.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Arrays;
import java.util.List;

public class TestMethodeReference {

  public static void main(String[] args) {
    List<String> fruits = Arrays.asList("melon", "abricot", "fraise", "cerise");
    afficher(fruits, String::format);
  }

  public static void afficher(List<String> liste, MonFormateur formateur) {
    int i = 0;
    for (String element : liste) {
      i++;
      System.out.print(formateur.formater("%3d %s%n", i, element));
    }
  }
}

@FunctionalInterface
interface MonFormateur {

  String formater(String format, Object... arguments);
}

L'exemple ci-dessous utilise une référence à la méthode statique compare() de la classe Integer pour trier un tableau en invoquant la méthode sort() de la classe Arrays. Cette méthode attend en paramètre le tableau à trier et une interface fonctionnelle de type Comparator.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;

public class TestReferenceMethodeStatique {

  public static void main(String[] args) {
    Integer[] valeurs = {10, 4, 2, 7, 5, 8, 1, 9, 3, 6};
    Arrays.sort(valeurs, Integer::compare);
    System.out.println(Arrays.deepToString(valeurs));
  }
}
Résultat :
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Attention, il n'est pas possible d'appliquer cette fonctionnalité si le tableau est un tableau d'entiers de type primitif.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;

public class TestReferenceMethodeStatique {

  public static void main(String[] args) {
    int[] valeurs = {10, 4, 2, 7, 5, 8, 1, 9, 3, 6};
    Arrays.sort(valeurs, Integer::compare);
    System.out.println(Arrays.deepToString(valeurs));
  }
}
Résultat :
C:\java\TestJava8\src>jav ac -cp . com/jmdoudoux/dej/java8/methode_reference/Tes
tReferenceMethodeStatique.java
com\jmdoudoux\dej\java8\methode_reference\TestReferenceMethodeStatique.java:9: e
rror: no suitable method found for sort(int[],Integer::compare)
    Arrays.sort(valeurs, Integer::compare);
          ^
    method Arrays.<T#1>sort(T#1[],Comparator<? super T#1>) is not applicable
      (inference variable T#1 has incompatible bounds
        equality constraints: int
        upper bounds: Integer,Object)
    method Arrays.<T#2>sort(T#2[],int,int,Comparator<? super T#2>) is not applic
able
      (cannot infer type-variable(s) T#2
        (actual and formal argument lists differ in length))
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>sort(T#1[],Comparator<? super T#1
>)
    T#2 extends Object declared in method <T#2>sort(T#2[],int,int,Comparator<? s
uper T#2>)
com\jmdoudoux\dej\java8\methode_reference\TestReferenceMethodeStatique.java:10:
error: incompatible types: int[] cannot be converted to Object[]
    System.out.println(Arrays.deepToString(valeurs));
                                           ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get
full output
2 errors

La signature de la méthode Compare() de la classe Integer est compatible avec l'interface fonctionnelle Comparator. Il est donc possible d'utiliser une référence statique sur cette méthode en paramètre de la méthode sort() de la classe Arrays.

De la même façon, il est possible d'utiliser une référence de méthode sur une méthode statique définie dans une classe de l'application tant que sa signature respecte celle de l'interface fonctionnelle.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;

public class TestReferenceMethodeStatique {

  public static void main(String[] args) {
    Integer[] valeurs = {10, 4, 2, 7, 5, 8, 1, 9, 3, 6};
    Arrays.sort(valeurs, TestReferenceMethodeStatique::comparerEntierAscendant);
    System.out.println(Arrays.deepToString(valeurs));
  }
  
  public static int comparerEntierAscendant(int a, int b) {
    return a - b;
  }
}

 

12.3.2. La référence à une méthode d'une instance

La référence d'une méthode d'instance permet d'offrir un raccourci syntaxique pour invoquer une méthode d'un objet.

La syntaxe est de la forme :

instance::nom_de_la_methode

Où :

  • instance est l'objet sur lequel la méthode est invoquée
  • nom_de_la_methode est le nom de la méthode à invoquer

Dans l'exemple ci-dessous, la classe ComparaisonPersonne propose deux méthodes pour comparer deux objets de type Personne selon deux critères.

Exemple :
package fr.jmdoudoux.dej.java8.methode_reference;

import fr.jmdoudoux.dej.java8.lambda.Personne;

public class ComparaisonPersonne {

  public int comparerParNom(Personne p1, Personne p2) {
    return p1.getNom().compareTo(p2.getNom());
  }
        
  public int comparerParPrenom(Personne p1, Personne p2) {
    return p1.getPrenom().compareTo(p2.getPrenom());
  }
}

La classe ci-dessous utilise une référence à une méthode d'une instance de type ComparaisonPersonne en paramètre de la méthode sort() de la classe Arrays pour trier un tableau de type Personne.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;

import fr.jmdoudoux.dej.java8.lambda.Personne;
import java.util.Arrays;

public class TestReferenceMethodeInstance {

  public static void main(String[] args) {
    Personne[] personnes = {
      new Personne("nom3", "Julien"),
      new Personne("nom1", "Thierry"),
      new Personne("nom2", "Alain")
    };
    ComparaisonPersonne comparaisonPersonne = new ComparaisonPersonne();
        
    Arrays.sort(personnes, comparaisonPersonne::comparerParNom);
    System.out.println(Arrays.deepToString(personnes));
    
    Arrays.sort(personnes, comparaisonPersonne::comparerParPrenom);
    System.out.println(Arrays.deepToString(personnes));    
  }
}
Résultat :
[Personne{nom=nom1, prenom=Thierry}, Personne{nom=nom2, prenom=Alain}, Personne{nom=nom3, 
prenom=Julien}]
[Personne{nom=nom2, prenom=Alain}, Personne{nom=nom3, prenom=Julien}, Personne{nom=nom1, 
prenom=Thierry}]

La classe de l'instance peut utiliser des generic tant que les types restent compatibles avec le contexte d'utilisation.

Exemple :
package fr.jmdoudoux.dej.java8.methode_reference;

import fr.jmdoudoux.dej.java8.lambda.Personne;

public class ComparaisonPersonne<T extends Personne> {

  public int comparerParNom(T p1, T p2) {
    return p1.getNom().compareTo(p2.getNom());
  }
        
  public int comparerParPrenom(T p1, T p2) {
    return p1.getPrenom().compareTo(p2.getPrenom());
  }
}

Le résultat est le même.

Le qualificateur de l'instance peut être le mot clé this pour désigner l'instance courante ou super pour désigner une référence sur la classe mère.

Ainsi this::equals est équivalent à l'expression lambda x -> this.equals(x).

 

12.3.3. La référence à une méthode d'une instance arbitraire d'un type

Une référence à une méthode d'instance sur un objet arbitraire d'un type permet d'invoquer une méthode d'une instance qui est précisée dans le premier paramètre fourni.

La syntaxe est de la forme :

nom_de_la_classe::nom_de_la_methode_d_instance

Où :

  • nom_de_la_classe est le type de l'instance
  • nom_de_la_methode_d_instance est le nom de la méthode à invoquer

Ce type de référence de méthodes ne précise pas explicitement le récepteur sur lequel la méthode sera invoquée. C'est le premier paramètre de la méthode de l'interface fonctionnelle qui correspond à ce récepteur.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;

public class TestReferenceMethodeUnbound {

  public static void main(String[] args) {
    String[] fruits = {"Melon", "abricot", "fraise", "cerise", "mytille"};
    Arrays.sort(fruits, String::compareToIgnoreCase);
    System.out.println(Arrays.deepToString(fruits));
  }
}
Résultat :
[abricot, cerise, fraise, Melon, mytille]

L'exemple ci-dessous est le même exemple dans lequel l'utilisation d'une référence de méthode est remplacée par une expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;

public class TestReferenceMethodeUnbound {

  public static void main(String[] args) {
    String[] fruits = {"Melon", "abricot", "fraise", "cerise", "mytillee"};
    Arrays.sort(fruits, (s1, s2) -> s1.compareToIgnoreCase(s2) );
    System.out.println(Arrays.deepToString(fruits));
  }
}

Le résultat est le même.

L'expression lambda correspondante à la référence de méthode de l'exemple ci-dessus attend deux paramètres de type String. Le premier sera l'instance sur laquelle la méthode est invoquée. Le second est passé en paramètre de la méthode.

Le corps de l'expression invoque la méthode compareToIgnoreCase() sur l'instance du premier paramètre en lui passant en paramètre le second.

L'exemple ci-dessous est le même exemple dans lequel l'utilisation d'une référence de méthode est remplacée par une classe anonyme interne.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.methode_reference;
      
import java.util.Arrays;
import java.util.Comparator;

public class TestReferenceMethodeUnbound {

  public static void main(String[] args) {
    String[] fruits = {"Melon", "abricot", "fraise", "cerise", "mytille"};
    Arrays.sort(fruits, new Comparator<String>() {
      @Override
      public int compare(String s1, String s2) {
        return s1.compareToIgnoreCase(s2);
      }
    });
    System.out.println(Arrays.deepToString(fruits));
  }
}

Le résultat est le même.

 

12.3.4. La référence à un constructeur

Il est possible de faire référence à un constructeur.

La syntaxe d'une référence à un constructeur est de la forme :

nom_classe::new

Il est inutile de préciser la surcharge du constructeur qui sera invoquée : le compilateur la détermine selon le contexte. La surcharge utilisée sera celle dont les paramètres correspondent le mieux à ceux de la méthode de l'interface fonctionnelle.

Une référence à un constructeur peut être passée en paramètre ou assignée à une variable d'un type d'une interface fonctionnelle.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Supplier;

public class TestConstructeurReference {

  public static void main(String[] args) {
    Supplier<Personne> supplier = Personne::new;
    System.out.println(supplier.get());
  }
}

Cet exemple est équivalent à celui ci-dessous qui utilise une expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Supplier;

public class TestConstructeurReference {

  public static void main(String[] args) {
     Supplier<Personne> supplier = () -> new Personne();
     System.out.println(supplier.get());
  }
}

Le tableau ci-dessous contient quelques exemples de références à un constructeur et leur équivalent sous la forme d'une expression lambda.

Référence à un constructeur

Expression lambda

Integer::new

(int valeur) -> new Integer(valeur)

ou

(String s) -> new Integer(s)

ArrayList<Personne>::new

() -> new ArrayList<Personne>()

String[]::new

(int size) -> new String[size]


Il est possible d'invoquer un constructeur possédant des paramètres : il faut pour cela que la méthode de l'interface fonctionnelle possède les paramètres qui correspondent à ceux du constructeur invoqué.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
@FunctionalInterface
public interface PersonneSupplier {
  Personne creerInstance(String nom, String prenom);
}
Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Supplier;

public class TestConstructeurReference {

  public static void main(String[] args) {
    PersonneSupplier supplier = Personne::new;
    Personne personne = supplier.creerInstance("nom1", "prenom1");
    System.out.println(personne);
  }
}
Résultat :
Personne{nom=nom1, prenom=prenom1}

Le constructeur qui sera invoqué dépend du contexte d'exécution : le compilateur va inférer les types des paramètres définis dans la méthode de l'interface fonctionnelle pour rechercher le constructeur possédant les mêmes types de paramètres.

Cet exemple est équivalent à celui ci-dessous qui utilise une expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Supplier;

public class TestConstructeurReference {

  public static void main(String[] args) {
    PersonneSupplier supplier = (nom, prenom) -> new Personne(nom, prenom);
    Personne personne = supplier.creerInstance("nom1", "prenom1");
    System.out.println(personne);
  }
}

Il est possible de préciser le ou les types si la classe est typée avec un generic.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.ArrayList;
import java.util.List;

public class TestConstructeurReference {

  public static void main(String[] args) {
      MaFabrique<List<String>> fabrique = ArrayList<String>::new;
   }
}

interface MaFabrique<T> {
  T creerInstance();
}

Il est possible d'utiliser une référence de constructeur pour un tableau.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

public class TestConstructeurReference {

  public static void main(String[] args) {
    MaFabrique<Integer[]> fabrique = Integer[]::new;
    Integer[] entiers = fabrique.creerInstance(10);
    System.out.println("taille = "+entiers.length);
  }
}

interface MaFabrique<T> {
  T creerInstance(int taille);
}
Résultat :
taille = 10

 

12.4. Les interfaces fonctionnelles

Une interface fonctionnelle (functional interface) est basiquement une interface dans laquelle une seule méthode abstraite est définie. Elle doit respecter certaines contraintes :

  • elle ne doit avoir qu'une seule méthode déclarée abstraite
  • les méthodes définies dans la classe Object ne sont pas prises en compte comme étant des méthodes abstraites
  • toutes les méthodes doivent être public
  • elle peut avoir des méthodes par défaut et static

Avant Java 8 un certain nombre d'interfaces ne définissaient qu'une seule méthode : ces interfaces sont désignées par le terme Single Abstract Method (SAM).

Avant Java 8, le JDK contenait déjà de nombreuses interfaces qui respectaient ces règles et sont donc des interfaces fonctionnelles, par exemple :

  • Comparator<T> qui définit la méthode int compare(T o1, T o2)
  • Callable<V> qui définit la méthode V call() throws exception
  • Runnable qui définit la méthode void run()
  • ActionListener qui définit la méthode void actionPerformed(ActionEvent)
  • ...

Elles se prêtent bien à l'utilisation d'une classe anonyme interne. Si elles ne sont pas modifiées alors elles sont utilisables comme interfaces fonctionnelles même si elles ne sont pas toutes annotées avec @FunctionalInterface puisque ce type de méthode respecte le contrat des interfaces fonctionnelles. Il est donc possible d'utiliser une expression lambda à la place d'une classe anonyme.

Exemple ( code Java 8 ) :
Consumer<String> afficher = (message) -> { System.out.println(message) };
BiConsumer<Integer, Integer> additionner = (x, y) -> x + y;
BiFunction<Integer, Integer, Long> additionner = (x, y) -> (long) x + y;

Une interface fonctionnelle ne définit qu'une seule méthode abstraite. Par exemple, l'interface Runnable qui ne définit que la méthode void run() est une interface fonctionnelle.

A partir de Java 8, il est possible d'utiliser une expression lambda à la place d'une classe anonyme. Une expression lambda peut être assignée explicitement ou implicitement à une interface fonctionnelle :

Exemple ( code Java 8 ) :
Runnable monTraitement = () -> System.out.println("bonjour");

Si le type n'est pas explicitement précisé dans le code, c'est le compilateur qui va déterminer implicitement l'interface fonctionnelle correspondante.

Exemple ( code Java 8 ) :
new Thread(() -> System.out.println("bonjour")).start();

Le compilateur va déduire l'interface fonctionnelle, dans ce cas l'interface Runnable.

Java 8 propose aussi en standard dans le JDK plusieurs interfaces fonctionnelles pour des besoins communs notamment dans le package java.util.function.

 

12.4.1. L'annotation @FunctionalInterface

Les interfaces fonctionnelles peuvent être annotées avec @FunctionalInterface : cette annotation permet de préciser l'intention que l'interface soit fonctionnelle.

L'utilisation de cette annotation est optionnelle mais elle apporte deux avantages :

  • elle indique au compilateur que l'interface est fonctionnelle : celui-ci va pouvoir vérifier que toutes les règles soient respectées pour qu'elle soit effectivement fonctionnelle
  • l'outil javadoc va utiliser l'annotation lors de la génération de la documentation

L'annotation @FunctionalInterface permet d'indiquer qu'une interface soit une interface fonctionnelle telle que définie dans la JLS. Elle ne peut être utilisée que sur une interface.

Exemple ( code Java 8 ) :
@FunctionalInterface
public interface MonInterface {
  public void traiter();
}

L'annotation @FunctionalInterface permet d'indiquer explicitement au compilateur l'intention de conception que l'interface est une interface fonctionnelle : celui-ci pourra alors vérifier que les contraintes sont respectées. Son utilisation est facultative car le compilateur peut déterminer automatiquement si une interface correspond aux contraintes des interfaces fonctionnelles.

Si l'annotation est utilisée sur une classe, une énumération, une annotation ou sur une interface qui ne respecte pas les contraintes d'une interface fonctionnelle alors le compilateur émettra une erreur.

Certaines interfaces de type SAM existant avant Java 8 n'ont pas été annotées avec @FunctionalInterface car elles ne sont pas considérées comme étant des fonctions même si leur caractéristique fera qu'elles seront considérées comme des interfaces fonctionnelles par le compilateur. C'est par exemple le cas des interfaces Comparable et Closeable.

 

12.4.2. La définition d'une interface fonctionnelle

Une interface peut contenir la définition de différents types de méthodes :

  • méthode abstraite
  • redéclarer une méthode de la classe Object pour par exemple utiliser un commentaire Javadoc particulier
  • méthode par défaut à partir de Java 8

Pour être une interface fonctionnelle, une interface ne doit avoir qu'une seule méthode abstraite déclarée. Elle peut avoir indifféremment aucune, une ou plusieurs redéfinitions de méthodes de la classe Object ou des méthodes par défaut.

Une interface fonctionnelle ne peut pas avoir plus d'une méthode abstraite. Si plusieurs méthodes abstraites sont définies dans l'interface, celle-ci ne sera pas une interface fonctionnelle.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
@FunctionalInterface
public interface MonInterfaceFonctionnelle {
  String executer();
  String effacer();
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/MonInterfaceFonctionnelle.java
com\jmdoudoux\dej\java8\lambda\MonInterfaceFonctionnelle.java:3: error: Unexpect
ed @FunctionalInterface annotation
@FunctionalInterface
^
  MonInterfaceFonctionnelle is not a functional interface
    multiple non-overriding abstract methods found in interface MonInterfaceFonc
tionnelle
1 error

Les méthodes publiques de la classe Object peuvent cependant être redéfinies dans une interface fonctionnelle.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
@FunctionalInterface
public interface MonInterfaceFonctionnelle {
  String executer();
  boolean equals(Object obj);
}

Ci-dessous, bien que la méthode clone() soit déclarée dans la classe Object, l'interface fonctionnelle ne compile pas car la méthode clone() de la classe Object est déclarée protected et non public.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
@FunctionalInterface
public interface MonInterfaceFonctionnelle {
  String executer();
  Object clone();
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/MonInterfaceFonctionnelle.java
com\jmdoudoux\dej\java8\lambda\MonInterfaceFonctionnelle.java:3: error: Unexpect
ed @FunctionalInterface annotation
@FunctionalInterface
^
  MonInterfaceFonctionnelle is not a functional interface
    multiple non-overriding abstract methods found in interface MonInterfaceFonc
tionnelle
1 error

Une interface fonctionnelle peut aussi contenir des méthodes statiques ou des méthodes par défaut.

 

12.4.3. L'utilisation d'une interface fonctionnelle

Les interfaces fonctionnelles définissent des types qui pourront être implémentés sous la forme d'expressions lambda ou de références de méthodes.

Chaque interface fonctionnelle ne possède qu'une seule méthode abstraite nommée méthode fonctionnelle (functional method) pour laquelle les paramètres et la valeur de retour doivent correspondre ou pouvoir être adaptés.

Une interface fonctionnelle définit une méthode qui pourra être utilisée pour passer en paramètre :

  • une référence sur une méthode d'une instance
  • une référence sur une méthode statique
  • une référence sur un constructeur
  • une expression lambda
  • une classe anonyme interne

Une interface fonctionnelle définit un type qui peut être utilisé dans plusieurs situations :

  • assignation
Exemple ( code Java 8 ) :
    Predicate<String> estVide = String::isEmpty;
  • directement en paramètre d'une méthode
Exemple ( code Java 8 ) :
monStream.filter(e -> e.getTaille() > 100)
  • cast
Exemple ( code Java 8 ) :
monStream.map((ToIntFunction) e -> e.getTaille())

Une interface fonctionnelle est avant tout une interface : elle peut donc être utilisée comme telle dans le code.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
@FunctionalInterface
public interface MonInterfaceFonctionnelle {
  void executer();
}
Exemple :
package fr.jmdoudoux.dej.java8.lambda;

public class TestMonInterface {

  public static void executer(MonInterfaceFonctionnelle monInterface) {
    monInterface.executer();
  }
  
  public static void main(String[] args) {
    executer(new MonInterfaceFonctionnelle() {

      @Override
      public void executer() {
        System.out.println("test");
      }
    });
  }
}

Il est aussi possible de profiter de la simplicité de la syntaxe d'une expression lambda puisqu'une expression lambda peut être utilisée partout où un objet de type interface fonctionnelle est attendu.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
public class TestMonInterface {

  public static void executer(MonInterfaceFonctionnelle monInterface) {
    monInterface.executer();
  }

  public static void main(String[] args) {
    executer(() -> System.out.println("test"));
  }
}

L'exemple ci-dessous tri un tableau de chaînes de caractères en utilisant la méthode sort() de la classe Arrays. Elle attend en paramètre le tableau à trier et une instance de type Comparator. Comme l'interface Comparator est une interface fonctionnelle, il est possible d'utiliser une expression lambda.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Arrays;

public class TestLambda {

  public static void main(String[] args) {

    String[] elements = new String[] { "aaa","zzz","fff","mmm"};
    Arrays.sort(elements, (o1, o2) -> o1.compareTo(o2));
    System.out.println(Arrays.toString(elements));
  }
}
Résultat :
[aaa, fff, mmm, zzz]

C'est le compilateur qui se charge d'effectuer toutes les opérations requises.

Il n'est pas possible d'assigner une expression lambda à un objet de type Object.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Arrays;

public class TestLambda {

  public static void main(String[] args) {

    Object tri = (o1, o2) -> o1.compareTo(o2);
  }    
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:9: error: incompatible types: Obj
ect is not a functional interface
    Object tri = (o1, o2) -> o1.compareTo(o2);
                 ^
1 error

Cette assignation n'est pas légale car le compilateur ne peut pas déterminer la méthode qui devra être invoquée puisque Object n'est pas une interface fonctionnelle.

Par contre, il est possible d'assigner à un objet d'un type d'une interface fonctionnelle une expression lambda qui respecte la déclaration de la méthode abstraite.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Arrays;
import java.util.Comparator;

public class TestLambda {

  public static void main(String[] args) {

    Comparator<String> tri = (o1, o2) -> o1.compareTo(o2);
    String[] elements = new String[] { "aaa","zzz","fff","mmm"};
    Arrays.sort(elements, tri);
    System.out.println(Arrays.toString(elements));
}

Il est aussi impératif de préciser le type generic de l'interface fonctionnelle pour permettre au compilateur d'inférer le type des paramètres de l'expression lambda et de vérifier que ce type possède bien une méthode compareTo().

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Comparator;

public class TestLambda {

  public static void main(String[] args) {
    Comparator tri = (o1, o2) -> o1.compareTo(o2);
  }
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:10: error: cannot find symbol
    Comparator tri = (o1, o2) -> o1.compareTo(o2);
                                   ^
  symbol:   method compareTo(Object)
  location: variable o1 of type Object
Note: com\jmdoudoux\dej\java8\lambda\TestLambda.java uses unchecked or unsafe op
erations.
Note: Recompile with -Xlint:unchecked for details.
1 error

Si une exception de type checked est levée dans le corps de l'expression lambda sans être gérée alors il est nécessaire que la méthode de l'interface fonctionnelle correspondante déclare la levée de cette exception sinon une erreur est émise à la compilation.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

public class TestLambda {

  public static void main(String[] args) {
    Runnable monTraitement = () -> {
      System.out.println("debut");
      Thread.sleep(1000);
      System.out.println("fin");
    };
  }
}
Résultat :
C:\java\TestJava8\src>javac -cp . com/jmdoudoux/dej/java8/lambda/TestLambda.java
com\jmdoudoux\dej\java8\lambda\TestLambda.java:14: error: unreported exception I
nterruptedException; must be caught or declared to be thrown
      Thread.sleep(1000);
                  ^
1 error

La méthode run() de l'interface Runnable ne déclare pas pouvoir lever une exception.

Pour résoudre ce problème, il y a plusieurs solutions :

  • ne pas lever d'exception
  • gérer l'exception dans le corps de l'expression lambda en utilisant un bloc try/catch
  • déclarer que la méthode de l'interface fonctionnelle peut lever une exception (impossible dans le cas de l'interface Runnable)
  • utiliser une autre interface fonctionnelle : dans le cas ci-dessous, Callable.
Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.concurrent.Callable;

public class TestLambda {

  public static void main(String[] args) {
    Callable monTraitement = () -> {
      System.out.println("debut");
      Thread.sleep(1000);
      System.out.println("fin");
      return null;
    };
  }
}

 

12.4.4. Les interfaces fonctionnelles du package java.util.function

Le package java.util.function propose en standard des interfaces fonctionnelles d'usage courant. Toutes les interfaces de ce package sont annotées avec @FunctionalInterface.

Le nom des interfaces du package java.util.function respecte une convention de nommage selon leur rôle :

  • Function : une fonction unaire qui permet de réaliser une transformation. Elle attend un ou plusieurs paramètres et renvoie une valeur. La méthode se nomme apply()
  • Consumer : une fonction qui permet de réaliser une action. Elle ne renvoie pas de valeur et attend un ou plusieurs paramètres. La méthode se nomme accept()
  • Predicate : une fonction qui attend un ou plusieurs paramètres et renvoie un booléen. La méthode se nomme test()
  • Supplier : une fonction qui renvoie une instance. Elle n'attend pas de paramètre et renvoie une valeur. La méthode se nomme get(). Elle peut être utilisé comme une fabrique

Le nom des interfaces fonctionnelles qui attendent en paramètre une ou plusieurs valeurs primitives sont préfixées par le type primitif.

Le nom des interfaces fonctionnelles qui renvoient une valeur primitive sont suffixées par toXXX.

Interface fonctionnelle

Description

BiConsumer<T,U>

Représente une opération qui requiert deux objets et ne renvoie aucun résultat

BiFunction<T,U,R>

Représente une opération qui requiert deux objets de type T et U et renvoie un résultat de type R

BinaryOperator<T>

Représente une opération qui attend deux paramètres de type T et renvoie une instance de type T

BiPredicate<T,U>

Représente un prédicat qui attend deux paramètres et renvoie un booléen

BooleanSupplier

Représente un fournisseur d'une valeur booléenne qui n'attend aucun paramètre

Consumer<T>

Représente un consommateur d'un unique paramètre qui ne renvoie aucune valeur

DoubleBinaryOperator

Représente une opération qui attend en paramètre deux valeurs de type double et renvoie une valeur de type double

DoubleConsumer

Représente un consommateur d'une valeur de type double

DoubleFunction<R>

Représente une fonction qui attend en paramètre une valeur de type double et renvoie un résultat de type R

DoublePredicate

Représente un prédicat qui attend en paramètre un argument de type double et renvoie un booléen

DoubleSupplier

Représente un fournisseur d'une valeur de type double

DoubleToIntFunction

Représente une opération qui attend en paramètre un double et renvoie un int comme résultat

DoubleToLongFunction

Représente une opération qui attend en paramètre un double et renvoie un long comme résultat

DoubleUnaryOperator

Représente une opération qui attend en paramètre un double et renvoie un double comme résultat

Function<T,R>

Représente une fonction qui attend un paramètre de type T et renvoie un résultat de type R

IntBinaryOperator

Représente une opération qui attend en paramètres deux valeurs de type int et renvoie une valeur de type int

IntConsumer

Représente un consommateur d'une valeur de type int

IntFunction<R>

Représente une fonction qui attend en paramètre une valeur de type int et renvoie un résultat de type R

IntPredicate

Représente un prédicat qui attend en paramètre un argument de type int et renvoie un booléen

IntSupplier

Représente un fournisseur de valeur qui n'attend aucun paramètre et renvoie un entier de type int

IntToDoubleFunction

Représente une fonction qui attend en paramètre une valeur de type int et renvoie un double

IntToLongFunction

Représente une fonction qui attend en paramètre une valeur de type int et renvoie un long

IntUnaryOperator

Représente une opération qui attend en paramètre un int et renvoie un int comme résultat

LongBinaryOperator

Représente une opération qui attend en paramètres deux valeurs de type long et renvoie une valeur de type long

LongConsumer

Représente un consommateur d'une valeur de type long

LongFunction<R>

Représente une fonction qui attend en paramètre une valeur de type long et renvoie un résultat de type R

LongPredicate

Représente un prédicat qui attend en paramètre un argument de type long et renvoie un booléen

LongSupplier

Représente un fournisseur de valeur qui n'attend aucun paramètre et renvoie un entier de type long

LongToDoubleFunction

Représente une fonction qui attend en paramètre une valeur de type long et renvoie un double

LongToIntFunction

Représente une fonction qui attend en paramètre une valeur de type long et renvoie un int

LongUnaryOperator

Représente une opération qui attend en paramètre un objet de type long et renvoie une valeur de type long

ObjDoubleConsumer<T>

Représente un consommateur qui attend en paramètres un objet de type T et un double

ObjIntConsumer<T>

Représente un consommateur qui attend en paramètres un objet de type T et un int

ObjLongConsumer<T>

Représente un consommateur qui attend en paramètres un objet de type T et un long

Predicate<T>

Représente un prédicat qui attend en paramètre un argument de type T et renvoie un booléen

Supplier<T>

Représente un fournisseur de valeur qui n'attend aucun paramètre et renvoie une instance de type T

ToDoubleBiFunction<T,U>

Représente une fonction qui attend deux paramètres de type T et U et renvoie un double

ToDoubleFunction<T>

Représente une fonction qui attend un paramètre de type T et renvoie un double

ToIntBiFunction<T,U>

Représente une fonction qui attend deux paramètres de type T et U et renvoie un int

ToIntFunction<T>

Représente une fonction qui attend un paramètre de type T et renvoie un int

ToLongBiFunction<T,U>

Représente une fonction qui attend un paramètre de type T et renvoie un long

ToLongFunction<T>

Représente une fonction qui attend un paramètre de type T et renvoie un long

UnaryOperator<T>

Représente une opération qui attend en paramètre un objet de type T et renvoie une instance de type T. Elle hérite de Function<T, T>


Ces interfaces fonctionnelles couvrent de nombreux besoins courants mais il est aussi possible de définir ses propres interfaces fonctionnelles.

 

12.4.4.1. Les interfaces fonctionnelles de types Consumer

Les interfaces fonctionnelles de type Consumer (Consumer, BiConsumer, DoubleConsumer, IntConsumer, LongConsumer, ObjDoubleConsumer, ObjIntConsumer, ObjLongConsumer) définissent des fonctions qui attendent différents types de paramètres et ne renvoient aucune valeur. Ne renvoyant aucune valeur, elles peuvent induire des effets de bords lors de l'exécution de leur traitement.

L'interface fonctionnelle Consumer<T> définit une fonction qui effectue une opération sur un objet et ne renvoie aucune valeur. Son exécution engendre généralement des effets de bord.

Elle définit la méthode fonctionnelle accept(T t) qui ne renvoie aucune valeur.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default Consumer andThen(Consumer< ? super T>

Renvoyer un Consumer qui exécute en séquence l'instance courante et celle fournie en paramètre


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Consumer;

public class TestConsumer {

  public static void main(String[] args) {
    Consumer<String> c = System.out::print;
    c.andThen(c).accept("bonjour ");
  }
}
Résultat :
bonjour bonjour

Lors de l'invocation de la méthode andThen(), si une exception est levée par un des deux Consumer, celle-ci est propagée dans la méthode englobante.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

public class TestConsumer {

  public static void main(String[] args) {
    AtomicInteger i = new AtomicInteger(0);
    Consumer<String> c = (x) -> {
      i.addAndGet(1);
      System.out.println(x);
      if (i.get() == 2) {
        throw new RuntimeException();
      }
    };
    c.andThen(c).accept("bonjour");
  }
}
Résultat :
bonjour
bonjour
Exception in thread "main" java.lang.RuntimeException
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer.lambda$main$0(TestConsumer.java:14)
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer$$Lambda$1/2536472.accept(Unknown Source)
       at java.util.function.Consumer.lambda$andThen$14(Consumer.java:65)
       at java.util.function.Consumer$$Lambda$2/32404285.accept(Unknown Source)
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer.main(TestConsumer.java:17)
Java Result: 1

Si une exception est levée lors de l'exécution du Consumer courant, le second Consumer n'est pas exécuté.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Consumer;

public class TestConsumer {

  public static void main(String[] args) {
    Consumer<String> c = (x) -> {
      System.out.println(x);
      throw new RuntimeException();
    };
    c.andThen(c).accept("bonjour");
  }
}
Résultat :
bonjour
Exception in thread "main" java.lang.RuntimeException
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer.lambda$main$0(TestConsumer.java:10)
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer$$Lambda$1/2536472.accept(Unknown Source)
       at java.util.function.Consumer.lambda$andThen$14(Consumer.java:65)
       at java.util.function.Consumer$$Lambda$2/13604864.accept(Unknown Source)
       at fr.jmdoudoux.dej.java8.lambda.TestConsumer.main(TestConsumer.java:12)

Elle lève une exception de type NullPointerException si le paramètre de la méthode andThen() est null.

L'interface fonctionnelle BiConsumer définit une opération qui attend deux paramètres et ne renvoie aucun résultat. C'est une spécialisation de l'interface fonctionnelle Consumer qui attend deux paramètres.

L'interface BiConsumer<T,U> est typé avec deux generics qui précisent respectivement le type du premier et du second argument de l'opération de la fonction.

Elle définit la méthode fonctionnelle accept(T t, U u) qui ne renvoie aucune valeur.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default BiConsumer andThen(BiConsumer< ? super T>

Renvoyer un BiConsumer qui exécute en séquence l'instance courante et celle fournie en paramètre


L'interface fonctionnelle DoubleConsumer définit une opération qui attend en paramètre une valeur de type double et ne renvoie aucun résultat. C'est une spécialisation de l'interface fonctionnelle Consumer qui attend une valeur flottante.

Elle définit la méthode fonctionnelle accept(double valeur) qui ne renvoie aucune valeur.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default DoubleConsumer andThen(DoubleConsumer)

Renvoyer un DoubleConsumer qui exécute en séquence l'instance courante et celle fournie en paramètre


L'interface fonctionnelle IntConsumer définit une opération qui attend en paramètre une valeur de type int et ne renvoie aucun résultat. C'est une spécialisation de l'interface fonctionnelle Consumer qui attend une valeur entière.

Elle définit la méthode fonctionnelle accept(int valeur) qui ne renvoie aucune valeur.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default IntConsumer andThen(IntConsumer)

Renvoyer un IntConsumer qui exécute en séquence l'instance courante et celle fournie en paramètre


L'interface fonctionnelle LongConsumer définit une opération qui attend en paramètre une valeur de type long et ne renvoie aucun résultat. C'est une spécialisation de l'interface fonctionnelle Consumer qui attend une valeur entière longue.

Elle définit la méthode fonctionnelle accept(long valeur) qui ne renvoie aucune valeur.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default LongConsumer andThen(LongConsumer)

Renvoyer un LongConsumer qui exécute en séquence l'instance courante et celle fournie en paramètre


L'interface fonctionnelle ObjDoubleConsumer<T> est une spécialisation de l'interface BiConsumer pour un objet et une valeur de type double. Elle définit la méthode fonctionnelle accept(T t, double value) qui ne renvoie rien.

L'interface fonctionnelle ObjIntConsumer<T> est une spécialisation de l'interface BiConsumer pour un objet et une valeur de type int. Elle définit la méthode fonctionnelle accept(T t, int value) qui ne renvoie rien.

L'interface fonctionnelle ObjLongConsumer<T> est une spécialisation de l'interface BiConsumer pour un objet et une valeur de type long. Elle définit la méthode fonctionnelle accept(T t, long value) qui ne renvoie rien.

 

12.4.4.2. Les interfaces fonctionnelles de type Function

Les interfaces fonctionnelles de type Function (Function, BiFunction, DoubleFunction, DoubleToIntFunction, DoubleToLongFunction, IntFunction, IntToDoubleFunction, IntToLongFunction, LongFunction, LongToDoubleFunction, LongToIntFunction, ToDoubleBiFunction, ToDoubleFunction, ToIntBiFunction, ToIntFunction, ToLongBiFunction, ToLongFunction, BinaryOperator, DoubleBinaryOperator, DoubleUnaryOperator, IntBinaryOperator, IntUnaryOperator, LongBinaryOperator, LongUnaryOperator, UnaryOperator) définissent des fonctions qui attendent différents types de paramètres et renvoient une valeur.

L'interface fonctionnelle Function<T, R> définit une fonction qui effectue une opération sur un objet de type T et renvoie une valeur de type R.

Elle définit la méthode fonctionnelle apply(T t) qui renvoie une valeur de type R.

Elle définit aussi plusieurs méthodes par défaut et static :

Méthode

Rôle

default <V> Function<T, V> andThen(Function< ? super R, ? extends V >

Renvoyer une Function qui exécute en séquence l'instance courante et celle fournie en paramètre

default <V> Function <V, R> compose(Function< ? super V, ? extends T>

Renvoyer une Function qui exécute l'instance fournie en paramètre et applique l'instance courante sur le résultat

static <T> Function<T, T> identity()

Renvoyer une Function qui renvoie toujours la valeur fournie en paramètre


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Function;

public class TestFunction {

  public static void main(String[] args) {
    Function<Integer,Long> doubler = (i) -> (long) i * 2;
    System.out.println(doubler.apply(2));
  }
}
Résultat :
4

Les méthodes andThen() et compose() permettent de mixer deux Function.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Function;

public class TestFunction {

  public static void main(String[] args) {
    Function<Long, Long> doubler = (i) -> {
      long resultat = (long) i * 2;
      System.out.println("doubler=" + resultat);
      return resultat;
    };

    Function<Long, Long> laMoitie = (i) -> {
      long resultat = i / 2;
      System.out.println("laMoitie=" + resultat);
      return resultat;
    };

    System.out.println(doubler.andThen(laMoitie).apply(3L));
    System.out.println(doubler.compose(laMoitie).apply(3L));
  }
}
Résultat :
doubler=6
laMoitie=3
3
laMoitie=1
doubler=2
2

La méthode identity() renvoie une instance de Function dont le but est simplement de renvoyer la valeur fournie en paramètre.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Function;

public class TestFunction {

  public static void main(String[] args) {
    Function<Long, Long> identite = Function.identity();
    System.out.println(identite.apply(3L));
  }
}
Résultat :
3

L'interface fonctionnelle BiFunction définit une opération qui attend deux paramètres et renvoie une valeur. C'est une spécialisation de l'interface fonctionnelle Function qui attend deux paramètres.

L'interface BiFunction<T,U,R> est typée avec trois generics qui précisent respectivement les types des deux arguments de l'opération de la fonction et le type de la valeur de retour.

Elle définit la méthode fonctionnelle accept(T t, U u) et renvoie une valeur de type R.

Elle définit aussi une méthode par défaut :

Méthode

Rôle

default <V> BiFunction<T, U, V> andThen(Function< ? super R, ? extends V>

Renvoyer une BiFunction qui exécute en séquence l'instance courante et celle fournie en paramètre


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.BiFunction;

public class TestBiFunction {

  public static void main(String[] args) {
    BiFunction<String, String, String> concatener = (x, y) -> x + y;
    System.out.println(concatener.apply("Bonjour", " Java"));
  }
}
Résultat :
Bonjour Java

L'interface fonctionnelle DoubleFunction<R> définit une opération qui attend en paramètre une valeur de type double et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur flottante.

L'interface DoubleFunction<R> est typée avec un generic qui précise le type de la valeur de retour.

Elle définit la méthode fonctionnelle apply(double valeur) qui renvoie une valeur de type R.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoubleFunction;

public class TestDoubleFunction {
  public static void main(String[] args) {
    DoubleFunction<String> formater = (x) -> String.format("%.2f", x);
    System.out.println(formater.apply(3.14116D));
  }
}
Résultat :
3,14

L'interface fonctionnelle DoubleToIntFunction définit une opération qui attend en paramètre une valeur de type double et renvoie un entier. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur flottante et retourne un entier.

Elle définit la méthode fonctionnelle applyAsInt(double valeur) qui renvoie une valeur de type int.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoubleToIntFunction;

public class TestDoubleToIntFunction {
  public static void main(String[] args) {
    DoubleToIntFunction dtif = (x) -> {return (int) x;};
    System.out.println(dtif.applyAsInt(3.14));
  }
}
Résultat :
3

L'interface fonctionnelle DoubleToLongFunction définit une opération qui attend en paramètre une valeur de type double et renvoie un entier long. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur flottante et retourne un entier long.

Elle définit la méthode fonctionnelle applyAsLong(double valeur) qui renvoie une valeur de type long.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoubleToLongFunction;

public class TestDoubleToLongFunction {

  public static void main(String[] args) {
    DoubleToLongFunction dtlf = (x) -> {return (long) x;};
    System.out.println(dtlf.applyAsLong(123456789012345.123D));
  }
}
Résultat :
123456789012345

L'interface fonctionnelle IntFunction définit une opération qui attend en paramètre une valeur de type int et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur entière.

L'interface IntFunction<R> est typée avec un generic qui précise le type de la valeur de retour.

Elle définit la méthode fonctionnelle apply(int valeur) qui renvoie une valeur de type R.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.IntFunction;

public class TestIntFunction {

  public static void main(String[] args) {
    IntFunction<String> formater = (x) -> String.format("%d m", x);
    System.out.println(formater.apply(3));
  }
}
Résultat :
3 m

L'interface fonctionnelle IntToDoubleFunction définit une opération qui attend en paramètre une valeur entière et renvoie une valeur flottante. C'est une spécialisation de l'interface fonctionnelle Function qui attend un entier et retourne une valeur flottante.

Elle définit la méthode fonctionnelle applyAsDouble(int valeur) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.IntToDoubleFunction;

public class TestIntToDoubleFunction {

  public static void main(String[] args) {
    IntToDoubleFunction cos = (x) -> Math.cos(x);
    System.out.println(cos.applyAsDouble(5));
  }
}
Résultat :
0.28366218546322625

L'interface fonctionnelle IntToLongFunction définit une opération qui attend en paramètre une valeur entière et renvoie une valeur entière longue. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur entière et retourne un entier long.

Elle définit la méthode fonctionnelle applyAsLong(int valeur) qui renvoie une valeur de type long.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.IntToLongFunction;

public class TestIntToLongFunction {

  public static void main(String[] args) {
    IntToLongFunction doubler = (x) -> (long) x * 2;
    System.out.println(doubler.applyAsLong(Integer.MAX_VALUE));
  }
}
Résultat :
4294967294

L'interface fonctionnelle LongFunction définit une opération qui attend en paramètre une valeur de type long et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur entière longue.

L'interface LongFunction<R> est typée avec un generic qui précise le type de la valeur de retour.

Elle définit la méthode fonctionnelle apply(long valeur) qui renvoie une valeur de type R.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.LongFunction;

public class TestLongFunction {
  public static void main(String[] args) {
    LongFunction<String> formater = (x) -> String.format("%d m", x);
    System.out.println(formater.apply(123456789012345L));
  }
}
Résultat :
123456789012345 m

L'interface fonctionnelle LongToDoubleFunction définit une opération qui attend en paramètre une valeur entière longue et renvoie une valeur flottante. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur entière longue et retourne une valeur flottante.

Elle définit la méthode fonctionnelle applyAsDouble(long valeur) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.LongToDoubleFunction;

public class TestLongToDoubleFunction {

  public static void main(String[] args) {
    LongToDoubleFunction sin = (x) -> Math.sin(x);
    System.out.println(sin.applyAsDouble(123456789012345L));
  }
}
Résultat :
-0.5986572942477425

L'interface fonctionnelle LongToIntFunction définit une opération qui attend en paramètre une valeur entière longue et renvoie une valeur entière. C'est une spécialisation de l'interface fonctionnelle Function qui attend une valeur entière longue et retourne une valeur entière.

Elle définit la méthode fonctionnelle applyAsInt(long valeur) qui renvoie une valeur de type int.

L'interface fonctionnelle ToDoubleBiFunction définit une opération qui attend en paramètre deux valeurs et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle BiFunction qui renvoie une valeur de type double.

L'interface ToDoubleBiFunction<T, U> est typée avec deux generic qui précisent le type de chacun des deux paramètres.

Elle définit la méthode fonctionnelle applyAsDouble(T t, U u) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.ToDoubleBiFunction;

public class TestToDoubleBiFunction {
  public static void main(String[] args) {
    ToDoubleBiFunction<Integer, Integer> trigo = 
      (x, y) -> Math.sin(x) + Math.cos(y);
    System.out.println(trigo.applyAsDouble(3, 5));
  }
}
Résultat :
0.42478219352309343

L'interface fonctionnelle ToDoubleFunction définit une opération qui attend en paramètre une valeur et renvoie une valeur flottante. C'est une spécialisation de l'interface fonctionnelle Function qui renvoie une valeur flottante.

L'interface ToDoubleFunction<T> est typée avec un generic qui précise le type du paramètre.

Elle définit la méthode fonctionnelle applyAsDouble(T t) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.ToDoubleFunction;

public class TestToDoubleFunction {

  public static void main(String[] args) {
    ToDoubleFunction<Integer> calculCosinus = (x) -> Math.cos(x);
    System.out.println(calculCosinus.applyAsDouble(10));
  }
}
Résultat :
-0.8390715290764524

L'interface fonctionnelle ToIntBiFunction définit une opération qui attend en paramètre deux valeurs et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle BiFunction qui renvoie un entier.

L'interface ToIntBiFunction<T, U> est typée avec deux generic qui précisent le type de chacun des deux paramètres.

Elle définit la méthode fonctionnelle applyAsInt(T t, U u) qui renvoie une valeur de type int.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.ToIntBiFunction;

public class TestToIntBiFunction {

  public static void main(String[] args) {
    ToIntBiFunction<String, String> somme = (x, y) -> 
      Integer.parseInt(x) + Integer.parseInt(y);
    System.out.println(somme.applyAsInt("123", "456"));
  }
}
Résultat :
579

L'interface fonctionnelle ToIntFunction définit une opération qui attend en paramètre une valeur et renvoie une valeur entière. C'est une spécialisation de l'interface fonctionnelle Function qui renvoie un entier.

L'interface ToIntFunction<T> est typée avec un generic qui précise le type du paramètre.

Elle définit la méthode fonctionnelle applyAsInt(T t) qui renvoie une valeur de type int.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.ToIntFunction;

public class TestToIntFunction {

  public static void main(String[] args) {
    ToIntFunction<String> convertEnInt = (x) -> Integer.parseInt(x);
    System.out.println(convertEnInt.applyAsInt("123"));
  }
}
Résultat :
123

L'interface fonctionnelle ToLongBiFunction définit une opération qui attend en paramètre deux valeurs et renvoie un résultat. C'est une spécialisation de l'interface fonctionnelle BiFunction qui renvoie un entier long.

L'interface ToLongBiFunction<T, U> est typée avec deux generic qui précisent le type de chacun des deux paramètres.

Elle définit la méthode fonctionnelle applyAsLong(T t, U u) qui renvoie une valeur de type long.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.ToLongBiFunction;

public class TestToLongBiFunction {

  public static void main(String[] args) {
    ToLongBiFunction<String, String> somme = 
      (x, y) -> Long.parseLong(x) + Long.parseLong(y);
    System.out.println(somme.applyAsLong("123", "456"));
  }
}
Résultat :
579

L'interface fonctionnelle ToLongFunction définit une opération qui attend en paramètre une valeur et renvoie une valeur entière longue. C'est une spécialisation de l'interface fonctionnelle Function qui renvoie un entier long.

L'interface ToLongFunction<T> est typée avec un generic qui précise le type du paramètre.

Elle définit la méthode fonctionnelle applyAsLong(T t) qui renvoie une valeur de type long.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.ToLongFunction;

public class TestToLongFunction {

  public static void main(String[] args) {
    ToLongFunction<String> convertEnLong = (x) -> Long.parseLong(x);
    System.out.println(convertEnLong.applyAsLong("1234567890123456"));
  }
}
Résultat :
1234567890123456

L'interface fonctionnelle BinaryOperator définit une opération qui attend deux paramètres et renvoie une valeur, ces éléments étant tous du même type.

L'interface BinaryOperator<T> est typée avec un generic qui précise le type des paramètres et de la valeur de retour. Elle hérite de l'interface fonctionnelle BiFunction<T, T, T>.

Elle définit la méthode fonctionnelle apply(T t, T u) qui renvoie une valeur de type T.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.BinaryOperator;

public class TestBinaryOperator {

  public static void main(String[] args) {
    BinaryOperator<Integer> ajout = (a, b) -> a + b;
    System.out.println(ajout.apply(10, 20));
  }
}
Résultat :
30

Elle définit aussi plusieurs méthodes par défaut et static :

Méthode

Rôle

static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator)

Renvoyer une BinaryOperator qui renverra le plus grand des deux objets selon le Comparator fourni en paramètre

static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator)

Renvoyer une BinaryOperator qui renverra le plus petit des deux objets selon le Comparator fourni en paramètre


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.Comparator;
import java.util.function.BinaryOperator;

public class TestBinaryOperator {

  public static void main(String[] args) {
    Comparator<Integer> comparateur = (a, b) -> b - a;
    
    BinaryOperator<Integer> biMin = BinaryOperator.minBy(comparateur);
    System.out.println(biMin.apply(2, 3));
    
    BinaryOperator<Integer> biMax = BinaryOperator.maxBy(comparateur);
    System.out.println(biMax.apply(2, 3));
  }
}
Résultat :
3
2

L'interface fonctionnelle DoubleBinaryOperator définit une opération qui attend deux paramètres et renvoie une valeur, tous ces éléments étant de type double. C'est une spécialisation de l'interface fonctionnelle BinaryOperator pour le type double.

Elle définit la méthode fonctionnelle applyAsDouble(double left, double rigth) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.DoubleBinaryOperator;

public class TestDoubleBinaryOperator {

  public static void main(String[] args) {
    DoubleBinaryOperator surfaceRectangle =
      (longueur, largeur) -> longueur * largeur;
    System.out.println(surfaceRectangle.applyAsDouble(10.5, 20.2));
  }
}
Résultat :
212.1

L'interface fonctionnelle DoubleUnaryOperator définit une opération qui attend un paramètre et renvoie une valeur, ces deux éléments étant de type double. C'est une spécialisation de l'interface fonctionnelle UnaryOperator pour le type double.

Elle définit la méthode fonctionnelle applyAsDouble(double operand) qui renvoie une valeur de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoubleUnaryOperator;

public class TestDoubleUnaryOperator {

  public static void main(String[] args) {
  
    DoubleUnaryOperator surfaceCarre = (cote) -> cote * cote;
    System.out.println(surfaceCarre.applyAsDouble(10.5));
  }
}
Résultat :
110.25

Elle définit aussi plusieurs méthodes par défaut et static :

Méthode

Rôle

default DoubleUnaryOperator andThen( DoubleUnaryOperator)

Renvoyer une DoubleUnaryOperator qui exécute en séquence l'instance courante et celle fournie en paramètre

default DoubleUnaryOperator compose(DoubleUnaryOperator)

Renvoyer une DoubleUnaryOperator qui exécute l'instance fournie en paramètre et applique l'instance courante sur le résultat

static DoubleUnaryOperator identity()

Renvoyer un DoubleUnaryOperator qui renvoie toujours la valeur fournie en paramètre


L'interface fonctionnelle IntBinaryOperator définit une opération qui attend deux paramètres et renvoie une valeur, ces deux éléments étant de type int. C'est une spécialisation de l'interface fonctionnelle BinaryOperator pour le type int.

Elle définit la méthode fonctionnelle applyAsInt(int left, int rigth) qui renvoie une valeur de type int.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.IntBinaryOperator;

public class TestIntBinaryOperator {
  public static void main(String[] args) {
    IntBinaryOperator surfaceRectangle = (longueur, largeur) -> longueur * largeur;
    System.out.println(surfaceRectangle.applyAsInt(10, 20));
  }
}
Résultat :
200

L'interface fonctionnelle IntUnaryOperator définit une opération qui attend un paramètre et renvoie une valeur, ces deux éléments étant de type int. C'est une spécialisation de l'interface fonctionnelle UnaryOperator pour le type int.

Elle définit la méthode fonctionnelle applyAsInt(int operand) qui renvoie une valeur de type int.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.IntUnaryOperator;

public class TestIntUnaryOperator {

  public static void main(String[] args) {
    IntUnaryOperator surfaceCarre = (cote) -> cote * cote;
    System.out.println(surfaceCarre.applyAsInt(10));
  }
}
Résultat :
100

Elle définit aussi plusieurs méthodes par défaut et static :

Méthode

Rôle

default IntUnaryOperator andThen(IntUnaryOperator)

Renvoyer une IntUnaryOperator qui exécute en séquence l'instance courante et celle fournie en paramètre

default IntUnaryOperator compose(IntUnaryOperator)

Renvoyer une IntUnaryOperator qui exécute l'instance fournie en paramètre et applique l'instance courante sur le résultat

static IntUnaryOperator identity()

Renvoyer une IntUnaryOperator qui renvoie toujours la valeur fournie en paramètre


L'interface fonctionnelle LongBinaryOperator définit une opération qui attend deux paramètres et renvoie une valeur, tous ces éléments étant de type long. C'est une spécialisation de l'interface fonctionnelle BinaryOperator pour le type long.

Elle définit la méthode fonctionnelle applyAsLong(long left, long right) qui renvoie une valeur de type entière longue.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.LongBinaryOperator;

public class TestLongBinaryOperator {

  public static void main(String[] args) {
    LongBinaryOperator surfaceRectangle = (longueur, largeur) -> longueur * largeur;
    System.out.println(surfaceRectangle.applyAsLong(10L, 20L));
  }
}
Résultat :
200

L'interface fonctionnelle LongUnaryOperator définit une opération qui attend un paramètre et renvoie une valeur, tous ces éléments étant de type long. C'est une spécialisation de l'interface fonctionnelle UnaryOperator pour le type long.

Elle définit la méthode fonctionnelle applyAsLong(long operand) qui renvoie une valeur de type entière longue.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;

import java.util.function.LongUnaryOperator;

public class TestLongUnaryOperator {

  public static void main(String[] args) {
    LongUnaryOperator surfaceCarre = (cote) -> cote * cote;
    System.out.println(surfaceCarre.applyAsLong(10L));
  }
}
Résultat :
100

Elle définit aussi plusieurs méthodes par défaut et static :

Méthode

Rôle

default LongUnaryOperator andThen(LongUnaryOperator)

Renvoyer une LongUnaryOperator qui exécute en séquence l'instance courante et celle fournie en paramètre

default LongUnaryOperator compose(LongUnaryOperator)

Renvoyer une LongUnaryOperator qui exécute l'instance fournie en paramètre et applique l'instance courante sur le résultat

static LongUnaryOperator identity()

Renvoyer une LongUnaryOperator qui renvoie toujours la valeur fournie en paramètre


L'interface fonctionnelle UnaryOperator définit une opération qui attend un paramètre et renvoie une valeur, tous ces éléments étant de même type.

L'interface UnaryOperator<T> est typée avec un generic qui précise le type du paramètre et de la valeur de retour. Elle hérite de l'interface fonctionnelle Function<T, T>.

Elle définit la méthode fonctionnelle apply(T t, T u) qui renvoie une valeur de type T.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.UnaryOperator;

public class TestUnaryOperator {
  public static void main(String[] args) {
    UnaryOperator<String> minuscule  = (c)-> c.toLowerCase();
    System.out.println(minuscule.apply("TEST"));
  }
}
Résultat :
test

Elle définit aussi une méthode static :

Méthode

Rôle

static <T> UnaryOperator<T> identity()

Renvoyer une UnaryOperator qui renvoie toujours la valeur fournie en paramètre

 

12.4.4.3. Les interfaces fonctionnelles de type Predicate

Les interfaces fonctionnelles de type Predicate (Predicate, BiPredicate, DoublePredicate, IntPredicate, LongPredicate) définissent des fonctions qui attendent différents types de paramètres et renvoient une valeur booléenne.

L'interface fonctionnelle Predicate<T> définit une fonction qui effectue une opération sur un objet et renvoie une valeur booléenne.

Elle définit la méthode fonctionnelle test(T t) qui renvoie un booléen.

Elle définit aussi plusieurs méthodes par défaut ou static :

Méthode

Rôle

default Predicate<T> and(Predicate< ? super T>)

Renvoyer un Predicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un ET logique sur leurs résultats. Si le prédicat courant est false alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.

static <T> Predicate<T> isEqual(Object)

Renvoyer un Predicate qui teste l'égalité de l'objet fourni en paramètre et de celui passé en paramètre de la méthode test() en utilisant la méthode Objects.equals()

default Predicate<t> negate()

Renvoyer un Predicate qui exécute l'instance courante en effectuant un NOT logique sur son résultat

default Predicate<T> or(Predicate< ? super T>)

Renvoyer un Precidate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un OU logique sur leurs résultats. Si le prédicat courant est true alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.Objects;
import java.util.function.Predicate;

public class TestPredicate {

  public static void main(String[] args) {
    Predicate<String> possedeTailleTrois = s -> s.length() == 3;
    Predicate<String> contientX = s -> s.contains("X");
    Predicate<String> estNonNull = Objects::nonNull;
    Predicate<String> contientXOuTaille3 = contientX.or(possedeTailleTrois);
    Predicate<String> estSMS = Predicate.isEqual("SMS");

    System.out.println("1 "+contientX.negate().test("WXYZ"));
    System.out.println("2 "+contientX.or(possedeTailleTrois).test("WWW"));
    System.out.println("2 "+contientX.or(possedeTailleTrois).test("WX"));
    System.out.println("3 "+contientX.and(possedeTailleTrois).test("WXY"));
    System.out.println("3 "+contientX.and(possedeTailleTrois).test("WWW"));
    System.out.println("4 "+estNonNull.test(null));
    System.out.println("5 "+estNonNull.and(contientX).and(possedeTailleTrois)
    .test("WWW"));
    System.out.println("5 "+estNonNull.and(contientX).and(possedeTailleTrois)
    .test("XX"));
    System.out.println("5 "+estNonNull.and(contientX).and(possedeTailleTrois)
    .test(null));
    System.out.println("6 "+estNonNull.and(contientXOuTaille3).test("WWW"));
    System.out.println("6 "+estNonNull.and(contientXOuTaille3).test("XX"));
    System.out.println("6 "+estNonNull.and(contientXOuTaille3).test(null));
    System.out.println("7 "+estNonNull.and(contientX.or(possedeTailleTrois))
    .test("WWW"));
    System.out.println("7 "+estNonNull.and(contientX.or(possedeTailleTrois))
    .test("XX"));
    System.out.println("7 "+estNonNull.and(contientX.or(possedeTailleTrois))
    .test(null));
    System.out.println("8 "+estSMS.test("SMS"));
    System.out.println("8 "+estSMS.test("ABC"));
    System.out.println("8 "+estSMS.test(null));
  }
}
Résultat :
1 false
2 true
2 true
3 true
3 false
4 false
5 false
5 false
5 false
6 true
6 true
6 false
7 true
7 true
7 false
8 true
8 false
8 false

L'interface fonctionnelle BiPredicate définit une opération qui attend deux paramètres et renvoie une valeur booléenne. C'est une spécialisation de l'interface fonctionnelle Predicate qui attend deux paramètres.

L'interface BiPredicate<T,U> est typé avec deux generic qui précisent respectivement le type du premier et du second argument de l'opération de la fonction.

Elle définit la méthode fonctionnelle test(T t, U u) qui renvoie un booléen.

Elle définit aussi plusieurs méthodes par défaut :

Méthode

Rôle

default BiPredicate<T, U> and(BiPredicate< ? super T, ? super U>)

Renvoyer un BiPredicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un ET logique sur leurs résultats. Si le prédicat courant est false alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.

default BiPredicate<T, U> negate()

Renvoyer un BiPredicate qui exécute l'instance courante en effectuant un NOT logique sur son résultat

default BiPredicate<T, U> or(BiPredicate< ? super T, ? super U>)

Renvoyer un BiPrecidate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un OU logique sur leurs résultats. Si le prédicat courant est true alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.BiPredicate;

public class TestBiPredicate {

  public static void main(String[] args) {
    BiPredicate<Integer, Integer> estSupOuEgal = (x, y) -> x >= y;
    BiPredicate<Integer, Integer> estLaMoitie = (x, y) -> x == y * 2;

    System.out.println("1 " + estSupOuEgal.test(2, 3));
    System.out.println("1 " + estSupOuEgal.test(3, 2));

    System.out.println("2 " + estSupOuEgal.and(estLaMoitie).test(4, 2));
    System.out.println("2 " + estSupOuEgal.and(estLaMoitie).test(3, 2));

    System.out.println("3 " + estSupOuEgal.negate().test(3, 2));

    System.out.println("4 " + estSupOuEgal.or(estLaMoitie).test(1, 1));
    System.out.println("4 " + estSupOuEgal.or(estLaMoitie).test(4, 2));
    System.out.println("4 " + estSupOuEgal.or(estLaMoitie).test(2, 4));
  }
}
Résultat :
1 false
1 true
2 true
2 false
3 false
4 true
4 true
4 false

L'interface fonctionnelle DoublePredicate définit une opération qui attend un nombre flottant et renvoie une valeur booléenne. C'est une spécialisation de l'interface fonctionnelle Predicate pour un nombre flottant.

Elle définit la méthode fonctionnelle test(double) qui renvoie un booléen.

Elle définit aussi des méthodes par défaut :

Méthode

Rôle

default DoublePredicate and(DoublePredicate)

Renvoyer un DoublePredicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un ET logique sur leurs résultats. Si le prédicat courant est false alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.

default DoublePredicate negate()

Renvoyer un DoublePredicate qui exécute l'instance courante en effectuant un NOT logique sur son résultat

default DoublePredicate or(DoublePredicate)

Renvoyer un DoublePredicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un OU logique sur leurs résultats. Si le prédicat courant est true alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoublePredicate;

public class TestDoublePredicate {

  private static final double DIX_MILLE = 10_000;

  public static void main(String[] args) {
    DoublePredicate estPositif = valeur -> valeur >= 0;
    DoublePredicate vautDixMille = valeur -> valeur == DIX_MILLE;

    System.out.println(estPositif.test(DIX_MILLE));
    System.out.println(estPositif.and(vautDixMille).test(DIX_MILLE));
    System.out.println(estPositif.negate().test(DIX_MILLE));
    System.out.println(estPositif.or(vautDixMille).test(100L));
  }
}
Résultat :
true
true
false
true

L'interface fonctionnelle LongPredicate définit une opération qui attend un entier et renvoie une valeur booléenne. C'est une spécialisation de l'interface fonctionnelle Predicate pour un entier long.

Elle définit la méthode fonctionnelle test(long) qui renvoie un booléen.

Elle définit aussi des méthodes par défaut :

Méthode

Rôle

default LongPredicate and(LongPredicate)

Renvoyer un LongPredicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un ET logique sur leurs résultats. Si le prédicat courant est false alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.

default LongPredicate negate()

Renvoyer un LongPredicate qui exécute l'instance courante en effectuant un NOT logique sur son résultat

default LongPredicate or(DoublePredicate)

Renvoyer un LongPredicate qui exécute en séquence l'instance courante et celle fournie en paramètre en effectuant un OU logique sur leurs résultats. Si le prédicat courant est true alors celui fourni en paramètre n'est pas évalué. Une exception levée par l'un des deux est propagée à l'appelant. Si une exception est levée lors de l'exécution du premier prédicat, le second n'est pas évalué.


Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.LongPredicate;

public class TestLongPredicate {

  private static final Long DIX_MILLE = 10_000L;
  
  public static void main(String[] args) {
    LongPredicate estPositif = valeur -> valeur >= 0;
    LongPredicate vautDixMille = valeur -> valeur == DIX_MILLE;

    System.out.println(estPositif.test(DIX_MILLE));
    System.out.println(estPositif.and(vautDixMille).test(DIX_MILLE));
    System.out.println(estPositif.negate().test(DIX_MILLE));
    System.out.println(estPositif.or(vautDixMille).test(100L));
  }
}
Résultat :
true
true
false
true

 

12.4.4.4. Les interfaces fonctionnelles de type Supplier

L'interface fonctionnelle Supplier définit une fonction qui renvoie une valeur dont le type correspond au type générique.

Elle définit la méthode fonctionnelle get() qui renvoie un objet de type T.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.Supplier;

public class TestSupplier {

  public static void main(String[] args) {
    Supplier<String> message = () -> "Bienvenue";
    System.out.println(message.get());
  }
}
Résultat :
Bienvenue

L'interface Supplier ne permet de renvoyer que des objets. Plusieurs interfaces fonctionnelles la spécialisent pour retourner des valeurs primitives : BooleanSupplier, DoubleSupplier, IntSupplier et LongSupplier.

L'interface fonctionnelle BooleanSupplier est une spécialisation de l'interface Supplier pour une valeur primitive de type booléenne.

Elle définit la méthode fonctionnelle getAsBoolean() qui renvoie une valeur de type boolean.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.BooleanSupplier;
      
public class TestBooleanSupplier {
      
  public static void main(String[] args) {
    int a = 10;
    int b = 12;
    BooleanSupplier aInferieurAB = () -> a <= b;
    System.out.println(aInferieurAB.getAsBoolean());
  }
}
Résultat :
true

L'interface fonctionnelle DoubleSupplier est une spécialisation de l'interface Supplier pour une valeur de type double.

Elle définit la méthode fonctionnelle getAsDouble() qui renvoie une valeur flottante de type double.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.DoubleSupplier;

public class TestDoubleSupplier {

  public static void main(String[] args) {
    DoubleSupplier pi = () -> 3.14116;
    System.out.println(pi.getAsDouble());
  }
}
Résultat :
3.14116

L'interface fonctionnelle IntSupplier est une spécialisation de l'interface Supplier pour une valeur de type int.

Elle définit la méthode fonctionnelle getAsInt() qui renvoie un entier de type int

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.IntSupplier;

public class TestIntSupplier {

  public static void main(String[] args) {
    int a = 10;
    int b = 12;
    IntSupplier aPlusB = () -> a + b;
    System.out.println(aPlusB.getAsInt());
  }
}
Résultat :
22

L'interface fonctionnelle LongSupplier est une spécialisation de l'interface Supplier pour une valeur de type long.

Elle définit la méthode fonctionnelle getAsLong() qui renvoie un entier de type long.

Exemple ( code Java 8 ) :
package fr.jmdoudoux.dej.java8.lambda;
      
import java.util.function.LongSupplier;
  
public class TestLongSupplier {

  public static void main(String[] args) {
    int a = 100000000;
    LongSupplier aAuCarre = () -> (long) a * a;
    System.out.println(aAuCarre.getAsLong());
  }
}
Résultat :
10000000000000000

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.