JavaRush /Blog Java /Random-FR /Expressions lambda avec exemples

Expressions lambda avec exemples

Publié dans le groupe Random-FR
Java est initialement un langage entièrement orienté objet. À l’exception des types primitifs, tout en Java est un objet. Même les tableaux sont des objets. Les instances de chaque classe sont des objets. Il n'existe pas une seule possibilité de définir une fonction séparément (en dehors d'une classe - traduction approximative ). Et il n’existe aucun moyen de transmettre une méthode comme argument ou de renvoyer un corps de méthode comme résultat d’une autre méthode. C'est comme ça. Mais c'était avant Java 8. Expressions lambda avec exemples - 1Depuis l'époque du bon vieux Swing, il était nécessaire d'écrire des classes anonymes lorsqu'il fallait transmettre certaines fonctionnalités à une méthode. Par exemple, voici à quoi ressemblait l’ajout d’un gestionnaire d’événements :
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Ici, nous voulons ajouter du code à l'écouteur d'événements de souris. Nous avons défini une classe anonyme MouseAdapteret créé immédiatement un objet à partir de celle-ci. De cette façon, nous avons transféré des fonctionnalités supplémentaires dans le addMouseListener. Bref, il n'est pas facile de faire passer une méthode (fonctionnalité) simple en Java via des arguments. Cette limitation a obligé les développeurs Java 8 à ajouter une fonctionnalité telle que les expressions Lambda à la spécification du langage.

Pourquoi Java a-t-il besoin d'expressions Lambda ?

Depuis le début, le langage Java n'a pas beaucoup évolué, à l'exception de choses comme les annotations, les génériques, etc. Tout d'abord, Java est toujours resté orienté objet. Après avoir travaillé avec des langages fonctionnels tels que JavaScript, on peut comprendre à quel point Java est strictement orienté objet et fortement typé. Les fonctions ne sont pas nécessaires en Java. À eux seuls, ils sont introuvables dans le monde Java. Dans les langages de programmation fonctionnels, les fonctions passent au premier plan. Ils existent par eux-mêmes. Vous pouvez les affecter à des variables et les transmettre via des arguments à d'autres fonctions. JavaScript est l'un des meilleurs exemples de langages de programmation fonctionnels. Vous pouvez trouver de bons articles sur Internet qui détaillent les avantages de JavaScript en tant que langage fonctionnel. Les langages fonctionnels disposent d'outils puissants tels que Closure, qui offrent un certain nombre d'avantages par rapport aux méthodes traditionnelles d'écriture d'applications. Une fermeture est une fonction à laquelle est attaché un environnement - une table qui stocke les références à toutes les variables non locales de la fonction. En Java, les fermetures peuvent être simulées via des expressions Lambda. Bien sûr, il existe des différences entre les fermetures et les expressions Lambda, et pas des moindres, mais les expressions lambda sont une bonne alternative aux fermetures. Dans son blog sarcastique et amusant, Steve Yegge décrit comment le monde de Java est strictement lié aux noms (entités, objets - environ trad. ). Si vous n'avez pas lu son blog, je vous le recommande. Il décrit de manière amusante et intéressante la raison exacte pour laquelle les expressions Lambda ont été ajoutées à Java. Les expressions Lambda apportent à Java des fonctionnalités qui manquaient depuis si longtemps. Les expressions Lambda apportent des fonctionnalités au langage, tout comme les objets. Bien que cela ne soit pas vrai à 100 %, vous pouvez constater que les expressions Lambda, même si elles ne sont pas des fermetures, offrent des fonctionnalités similaires. Dans un langage fonctionnel, les expressions lambda sont des fonctions ; mais en Java, les expressions lambda sont représentées par des objets et doivent être associées à un type d'objet spécifique appelé interface fonctionnelle. Ensuite, nous verrons de quoi il s'agit. L'article de Mario Fusco « Pourquoi nous avons besoin d'une expression Lambda en Java » décrit en détail pourquoi tous les langages modernes ont besoin de capacités de fermeture.

Introduction aux expressions Lambda

Les expressions Lambda sont des fonctions anonymes (ce n'est peut-être pas une définition correcte à 100 % pour Java, mais cela apporte une certaine clarté). En termes simples, il s'agit d'une méthode sans déclaration, c'est-à-dire sans modificateurs d'accès, renvoyant la valeur et le nom. Bref, ils permettent d’écrire une méthode et de l’utiliser immédiatement. C'est particulièrement utile dans le cas d'un appel de méthode unique, car réduit le temps nécessaire pour déclarer et écrire une méthode sans avoir à créer une classe. Les expressions Lambda en Java ont généralement la syntaxe suivante (аргументы) -> (тело). Par exemple:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Vous trouverez ci-dessous quelques exemples d'expressions Lambda réelles :
(int a, int b) -> {  return a + b; }

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

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Structure des expressions Lambda

Étudions la structure des expressions lambda :
  • Les expressions Lambda peuvent avoir 0 ou plusieurs paramètres d'entrée.
  • Le type de paramètres peut être spécifié explicitement ou peut être obtenu à partir du contexte. Par exemple ( int a) peut être écrit comme ceci ( a)
  • Les paramètres sont mis entre parenthèses et séparés par des virgules. Par exemple ( a, b) ou ( int a, int b) ou ( String a, int b, float c)
  • S'il n'y a pas de paramètres, vous devez utiliser des parenthèses vides. Par exemple() -> 42
  • Lorsqu'il n'y a qu'un seul paramètre, si le type n'est pas spécifié explicitement, les parenthèses peuvent être omises. Exemple:a -> return a*a
  • Le corps d'une expression Lambda peut contenir 0 ou plusieurs expressions.
  • Si le corps est constitué d'une seule instruction, il ne peut pas être placé entre accolades et la valeur de retour peut être spécifiée sans le mot-clé return.
  • Sinon, les accolades sont obligatoires (bloc de code) et la valeur de retour doit être précisée à la fin à l'aide d'un mot clé return(sinon le type de retour sera void).

Qu'est-ce qu'une interface fonctionnelle

En Java, les interfaces Marker sont des interfaces sans déclarer de méthodes ni de champs. En d’autres termes, les interfaces de jetons sont des interfaces vides. De même, les interfaces fonctionnelles sont des interfaces sur lesquelles une seule méthode abstraite est déclarée. java.lang.Runnableest un exemple d'interface fonctionnelle. Il ne déclare qu'une seule méthode void run(). Il existe également une interface ActionListener- également fonctionnelle. Auparavant, nous devions utiliser des classes anonymes pour créer des objets implémentant une interface fonctionnelle. Avec les expressions Lambda, tout est devenu plus simple. Chaque expression lambda peut être implicitement liée à une interface fonctionnelle. Par exemple, vous pouvez créer une référence à Runnableune interface, comme illustré dans l'exemple suivant :
Runnable r = () -> System.out.println("hello world");
Ce type de conversion se fait toujours implicitement lorsque l'on ne spécifie pas d'interface fonctionnelle :
new Thread(
    () -> System.out.println("hello world")
).start();
Dans l'exemple ci-dessus, le compilateur crée automatiquement une expression lambda en tant qu'implémentation Runnablede l'interface à partir du constructeur de classe Thread: public Thread(Runnable r) { }. Voici quelques exemples d'expressions lambda et d'interfaces fonctionnelles correspondantes :
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
L'annotation @FunctionalInterfaceajoutée dans Java 8 selon la spécification du langage Java vérifie si l'interface déclarée est fonctionnelle. De plus, Java 8 inclut un certain nombre d'interfaces fonctionnelles prêtes à l'emploi à utiliser avec les expressions Lambda. @FunctionalInterfacegénérera une erreur de compilation si l'interface déclarée n'est pas fonctionnelle. Voici un exemple de définition d'une interface fonctionnelle :
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Comme le suggère la définition, une interface fonctionnelle ne peut avoir qu’une seule méthode abstraite. Si vous essayez d'ajouter une autre méthode abstraite, vous obtiendrez une erreur de compilation. Exemple:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
Erreur
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
Conclusion:
Worker вызван через анонимный класс
Worker вызван через Lambda
Ici, nous avons défini notre propre interface fonctionnelle et utilisé une expression lambda. La méthode execute()est capable d'accepter des expressions lambda comme argument.

Exemples d'expressions Lambda

La meilleure façon de comprendre les expressions Lambda est d'examiner quelques exemples : Un flux Threadpeut être initialisé de deux manières :
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
La gestion des événements dans Java 8 peut également être effectuée via des expressions Lambda. Voici deux manières d'ajouter un gestionnaire d'événements ActionListenerà un composant d'interface utilisateur :
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
Un exemple simple d'affichage de tous les éléments d'un tableau donné. Notez qu'il existe plusieurs façons d'utiliser une expression lambda. Ci-dessous, nous créons une expression lambda de la manière habituelle en utilisant la syntaxe des flèches, et nous utilisons également l'opérateur double deux-points (::)qui, dans Java 8, convertit une méthode régulière en une expression lambda :
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
Dans l'exemple suivant, nous utilisons une interface fonctionnelle Predicatepour créer un test et imprimer les éléments qui réussissent ce test. De cette façon, vous pouvez mettre de la logique dans les expressions lambda et faire des choses en fonction de celle-ci.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
Conclusion:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
En bricolant les expressions Lambda, vous pouvez afficher le carré de chaque élément de la liste. Notez que nous utilisons la méthode stream()pour convertir une liste régulière en flux. Java 8 fournit une classe géniale Stream( java.util.stream.Stream). Il contient des tonnes de méthodes utiles avec lesquelles vous pouvez utiliser des expressions lambda. Nous transmettons une expression lambda x -> x*xà la méthode map(), qui l'applique à tous les éléments du flux. Après quoi, nous forEachimprimons tous les éléments de la liste.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Étant donné une liste, vous devez imprimer la somme des carrés de tous les éléments de la liste. Les expressions Lambda vous permettent d'y parvenir en écrivant une seule ligne de code. Cet exemple utilise la méthode de convolution (réduction) reduce(). Nous utilisons une méthode map()pour mettre au carré chaque élément, puis utilisons une méthode reduce()pour réduire tous les éléments en un seul nombre.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

La différence entre les expressions Lambda et les classes anonymes

La principale différence réside dans l'utilisation du mot-clé this. Pour les classes anonymes, le mot-clé ' ' thisdésigne un objet de la classe anonyme, tandis que dans une expression lambda, ' this' désigne un objet de la classe dans laquelle l'expression lambda est utilisée. Une autre différence réside dans la manière dont ils sont compilés. Java compile les expressions lambda et les convertit en privateméthodes de classe. Cela utilise l' instruction Invokedynamic , introduite dans Java 7 pour la liaison de méthode dynamique. Tal Weiss a décrit dans son blog comment Java compile les expressions lambda en bytecode

Conclusion

Mark Reinhold (architecte en chef d'Oracle) a qualifié les expressions Lambda de changement le plus important jamais réalisé dans le modèle de programmation - encore plus important que les génériques. Il doit avoir raison, parce que... ils donnent aux programmeurs Java les capacités fonctionnelles du langage de programmation que tout le monde attendait. Outre des innovations telles que les méthodes d'extension virtuelle, les expressions Lambda vous permettent d'écrire du code de très haute qualité. J'espère que cet article vous a donné un aperçu de Java 8. Bonne chance :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION