A qui s'adresse cet article ?
  • C'est pour les personnes qui ont lu la première partie de cet article ;
  • C'est pour les personnes qui pensent déjà bien connaître Java Core, mais qui n'ont aucune idée des expressions lambda en Java. Ou peut-être ont-ils entendu parler des expressions lambda, mais les détails manquent.
  • C'est pour les personnes qui ont une certaine compréhension des expressions lambda, mais qui sont toujours découragées par elles et peu habituées à les utiliser.
Si vous ne correspondez pas à l'une de ces catégories, vous pourriez trouver cet article ennuyeux, imparfait ou généralement pas votre tasse de thé. Dans ce cas, n'hésitez pas à passer à autre chose ou, si vous connaissez bien le sujet, n'hésitez pas à faire des suggestions dans les commentaires sur la façon dont je pourrais améliorer ou compléter l'article. Une explication des expressions lambda en Java.  Avec des exemples et des tâches.  Partie 2 - 1Le matériel ne prétend pas avoir de valeur académique, encore moins de nouveauté. Bien au contraire : je vais essayer de décrire le plus simplement possible des choses complexes (pour certaines personnes). Une demande d'explication de l'API Stream m'a inspiré pour écrire ceci. J'y ai réfléchi et j'ai décidé que certains de mes exemples de flux seraient incompréhensibles sans une compréhension des expressions lambda. Nous allons donc commencer par les expressions lambda.

Accès aux variables externes

Ce code compile-t-il avec une classe anonyme ?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
Non. La counter variable doit être final. Ou sinon final, alors au moins il ne peut pas changer sa valeur. Le même principe s'applique aux expressions lambda. Ils peuvent accéder à toutes les variables qu'ils peuvent "voir" depuis l'endroit où elles sont déclarées. Mais un lambda ne doit pas les modifier (leur attribuer une nouvelle valeur). Cependant, il existe un moyen de contourner cette restriction dans les classes anonymes. Créez simplement une variable de référence et modifiez l'état interne de l'objet. Ce faisant, la variable elle-même ne change pas (pointe vers le même objet) et peut être marquée en toute sécurité comme final.

final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() { 

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Ici, notre countervariable est une référence à un AtomicIntegerobjet. Et la incrementAndGet()méthode est utilisée pour changer l'état de cet objet. La valeur de la variable elle-même ne change pas pendant l'exécution du programme. Il pointe toujours sur le même objet, ce qui nous permet de déclarer la variable avec le mot clé final. Voici les mêmes exemples, mais avec des expressions lambda :

int counter = 0;
Runnable r = () -> counter++;
Cela ne compilera pas pour la même raison que la version avec une classe anonyme :  counterne doit pas changer pendant l'exécution du programme. Mais tout va bien si nous le faisons comme ceci:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Cela s'applique également aux méthodes d'appel. Dans les expressions lambda, vous pouvez non seulement accéder à toutes les variables "visibles", mais également appeler toutes les méthodes accessibles.

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
Bien qu'il staticMethod()soit privé, il est accessible à l'intérieur de la main()méthode, il peut donc également être appelé à l'intérieur d'un lambda créé dans la mainméthode.

Quand une expression lambda est-elle exécutée ?

Vous trouverez peut-être la question suivante trop simple, mais vous devriez quand même la poser : quand le code à l'intérieur de l'expression lambda sera-t-il exécuté ? Quand est-il créé ? Ou quand il s'appelle (ce qui n'est pas encore connu) ? Ceci est assez facile à vérifier.

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
Sortie écran :

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Vous pouvez voir que l'expression lambda a été exécutée à la toute fin, après la création du thread et uniquement lorsque l'exécution du programme atteint la run()méthode. Certainement pas lorsqu'il est déclaré. En déclarant une expression lambda, nous avons seulement créé un Runnableobjet et décrit le run()comportement de sa méthode. La méthode elle-même est exécutée beaucoup plus tard.

Références de méthode ?

Les références de méthode ne sont pas directement liées aux lambdas, mais je pense qu'il est logique d'en dire quelques mots dans cet article. Supposons que nous ayons une expression lambda qui ne fait rien de spécial, mais appelle simplement une méthode.

x -> System.out.println(x)
Il reçoit quelques xappels et seulement System.out.println(), en passant x. Dans ce cas, nous pouvons le remplacer par une référence à la méthode souhaitée. Comme ça:

System.out::println
C'est vrai - pas de parenthèses à la fin ! Voici un exemple plus complet :

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
Dans la dernière ligne, nous utilisons la forEach()méthode, qui prend un objet qui implémente l' Consumerinterface. Encore une fois, il s'agit d'une interface fonctionnelle, n'ayant qu'une seule void accept(T t)méthode. En conséquence, nous écrivons une expression lambda qui a un paramètre (parce qu'elle est typée dans l'interface elle-même, nous ne spécifions pas le type de paramètre ; nous indiquons seulement que nous l'appellerons x). Dans le corps de l'expression lambda, nous écrivons le code qui sera exécuté lorsque la accept()méthode sera appelée. Ici, nous affichons simplement ce qui s'est retrouvé dans la xvariable. Cette même forEach()méthode parcourt tous les éléments de la collection et appelle la accept()méthode sur l'implémentation de laConsumerinterface (notre lambda), en transmettant chaque élément de la collection. Comme je l'ai dit, nous pouvons remplacer une telle expression lambda (qui classe simplement une méthode différente) par une référence à la méthode souhaitée. Ensuite, notre code ressemblera à ceci :

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
L'essentiel est que les paramètres des méthodes println()et accept()correspondent. Parce que la println()méthode peut accepter n'importe quoi (elle est surchargée pour tous les types primitifs et tous les objets), au lieu d'expressions lambda, nous pouvons simplement passer une référence à la println()méthode à forEach(). Puis forEach()prendra chaque élément de la collection et le passera directement à la println()méthode. Pour toute personne rencontrant cela pour la première fois, veuillez noter que nous n'appelons pas System.out.println()(avec des points entre les mots et avec des parenthèses à la fin). Au lieu de cela, nous passons une référence à cette méthode. Si nous écrivons ceci

strings.forEach(System.out.println());
nous aurons une erreur de compilation. Avant l'appel à forEach(), Java voit qui System.out.println()est appelé, il comprend donc que la valeur de retour est voidet essaiera de passer voidà forEach(), qui attend à la place un Consumerobjet.

Syntaxe des références de méthode

C'est assez simple :
  1. Nous passons une référence à une méthode statique comme celle-ci :ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. Nous passons une référence à une méthode non statique en utilisant un objet existant, comme ceci :objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            Main instance = new Main(); 
            strings.forEach(instance::nonStaticMethod); 
        } 
    
        private void nonStaticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  3. Nous passons une référence à une méthode non statique en utilisant la classe qui l'implémente comme suit :ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            users.forEach(User::print); 
        } 
    
        private static class User { 
            private String name; 
    
            private User(String name) { 
                this.name = name; 
            } 
    
            private void print() { 
                System.out.println(name); 
            } 
        } 
    }
    
  4. Nous passons une référence à un constructeur comme celui-ci :ClassName::new

    Les références de méthode sont très pratiques lorsque vous disposez déjà d'une méthode qui fonctionnerait parfaitement comme rappel. Dans ce cas, au lieu d'écrire une expression lambda contenant le code de la méthode, ou d'écrire une expression lambda qui appelle simplement la méthode, nous lui passons simplement une référence. Et c'est tout.

Une distinction intéressante entre les classes anonymes et les expressions lambda

Dans une classe anonyme, le thismot clé pointe vers un objet de la classe anonyme. Mais si nous l'utilisons à l'intérieur d'un lambda, nous accédons à l'objet de la classe contenante. Celui où nous avons en fait écrit l'expression lambda. Cela se produit parce que les expressions lambda sont compilées dans une méthode privée de la classe dans laquelle elles sont écrites. Je ne recommanderais pas d'utiliser cette "fonctionnalité", car elle a un effet secondaire et contredit les principes de la programmation fonctionnelle. Cela dit, cette approche est tout à fait cohérente avec la POO. ;)

Où ai-je obtenu mes informations et que devriez-vous lire d'autre ?

Et, bien sûr, j'ai trouvé une tonne de choses sur Google :)