JavaRush /Blog Java /Random-FR /Populaire sur les expressions lambda en Java. Avec des ex...
Стас Пасинков
Niveau 26
Киев

Populaire sur les expressions lambda en Java. Avec des exemples et des tâches. Partie 2

Publié dans le groupe Random-FR
À qui s’adresse cet article ?
  • Pour ceux qui lisent la première partie de cet article ;

  • Pour ceux qui pensent déjà bien connaître Java Core, mais n’ont aucune idée des expressions lambda en Java. Ou peut-être avez-vous déjà entendu parler des lambdas, mais sans détails.

  • pour ceux qui ont une certaine compréhension des expressions lambda, mais qui ont toujours peur et sont inhabituels de les utiliser.

Accès aux variables externes

Ce code sera-t-il compilé avec une classe anonyme ?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Non. La variable counterdoit être final. Ou pas forcément final, mais en aucun cas il ne peut changer sa valeur. Le même principe est utilisé dans les expressions lambda. Ils ont accès à toutes les variables qui leur sont « visibles » depuis l'endroit où elles sont déclarées. Mais le lambda ne doit pas les modifier (attribuer une nouvelle valeur). Certes, il existe une option pour contourner cette limitation dans les classes anonymes. Il suffit de créer une variable de type référence et de modifier l'état interne de l'objet. Dans ce cas, la variable elle-même pointera vers le même objet, et dans ce cas, vous pouvez l'indiquer en toute sécurité comme final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Ici, notre variable counterest une référence à un objet de type AtomicInteger. Et pour changer l'état de cet objet, la méthode est utilisée incrementAndGet(). La valeur de la variable elle-même ne change pas pendant l'exécution du programme et pointe toujours vers le même objet, ce qui nous permet de déclarer une variable immédiatement avec le mot-clé final. Les mêmes exemples, mais avec des expressions lambda :
int counter = 0;
Runnable r = () -> counter++;
Elle ne se compile pas pour la même raison que l'option avec une classe anonyme : counterelle ne doit pas changer pendant l'exécution du programme. Mais comme ça, tout va bien :
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Cela s'applique également aux méthodes d'appel. Depuis une expression lambda, vous pouvez non seulement accéder à toutes les variables « visibles », mais également appeler les méthodes auxquelles vous avez accès.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Bien que la méthode staticMethod()soit privée, elle est « accessible » pour être appelée à l’intérieur de la méthode main(), elle est donc également accessible pour être appelée depuis l’intérieur du lambda créé dans la méthode main.

Le moment de l'exécution du code de l'expression lambda

Cette question peut vous sembler trop simple, mais elle vaut la peine de se poser : quand le code contenu dans l'expression lambda sera-t-il exécuté ? Au moment de la création ? Ou au moment où (on ne sait toujours pas où) on l'appellera ? C'est assez simple à vérifier.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Sortie affichée :
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
On peut voir que le code de l'expression lambda a été exécuté à la toute fin, après la création du thread et seulement lorsque le processus d'exécution du programme a atteint l'exécution réelle de la méthode run(). Et pas du tout au moment de son annonce. En déclarant une expression lambda, nous avons uniquement créé un objet du type Runnableet décrit le comportement de sa méthode run(). La méthode elle-même a été lancée bien plus tard.

Références de méthodes ?

Pas directement lié aux lambdas eux-mêmes, mais je pense qu'il serait logique d'en dire quelques mots dans cet article. Disons que nous avons une expression lambda qui ne fait rien de spécial, mais appelle simplement une méthode.
x -> System.out.println(x)
Ils lui ont tendu quelque chose х, et cela l'a simplement appelé System.out.println()et lui a passé là х. Dans ce cas, nous pouvons le remplacer par un lien vers la méthode dont nous avons besoin. Comme ça:
System.out::println
Oui, sans les parenthèses à la fin ! Exemple plus complet :
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Sur la dernière ligne, nous utilisons une méthode forEach()qui accepte un objet d'interface Consumer. Il s'agit encore d'une interface fonctionnelle avec une seule méthode void accept(T t). En conséquence, nous écrivons une expression lambda qui prend un paramètre (puisqu'il est tapé dans l'interface elle-même, nous n'indiquons pas le type du paramètre, mais indiquons qu'il sera appelé х). Dans le corps de l'expression lambda, nous écrivons le code qui sera exécuté lorsque la méthode sera appelée accept(). Ici on affiche simplement à l'écran ce qu'il y a dans la variable х. La méthode elle-même forEach()parcourt tous les éléments de la collection, appelle Consumerla méthode de l'objet d'interface qui lui est passé (notre lambda) accept(), où il transmet chaque élément de la collection. Comme je l'ai déjà dit, il s'agit d'une expression lambda (appelant simplement une autre méthode) que nous pouvons remplacer par une référence à la méthode dont nous avons besoin. Notre code ressemblera alors à ceci :
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
L'essentiel est que les paramètres acceptés des méthodes (println()et accept()). Puisque la méthode println()peut tout accepter (elle est surchargée pour toutes les primitives et pour tous les objets), au lieu d'une expression lambda, nous pouvons passer forEach()simplement une référence à la méthode println(). Ensuite forEach(), elle prendra chaque élément de la collection et le passera directement à la méthode println()Pour ceux qui rencontrent cela pour la première fois, veuillez noter Veuillez noter que nous n'appelons pas la méthode System.out.println()(avec des points entre les mots et avec des crochets à la fin), mais plutôt nous transmettons la référence à cette méthode elle-même.
strings.forEach(System.out.println());
nous aurons une erreur de compilation. Car avant l'appel forEach(), Java verra qu'il est appelé System.out.println(), il comprendra qu'il est renvoyé voidet tentera voidde le transmettre à forEach()l'objet de type qui l'attend Consumer.

Syntaxe d'utilisation des références de méthodes

C'est assez simple :
  1. Passer une référence à une méthode statiqueNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Passer une référence à une méthode non statique à l'aide d'un objet existantNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            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 dans laquelle une telle méthode est implémentéeNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            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. Passer un lien au constructeur NameКласса::new
    L'utilisation de liens de méthode est très pratique lorsqu'il existe une méthode toute faite dont vous êtes entièrement satisfait et que vous souhaitez l'utiliser comme rappel. Dans ce cas, au lieu d'écrire une expression lambda avec le code de cette méthode, ou une expression lambda où nous appelons simplement cette méthode, nous lui passons simplement une référence. C'est tout.

Différence intéressante entre la classe anonyme et l'expression lambda

Dans une classe anonyme, le mot-clé thispointe vers un objet de cette classe anonyme. Et si nous l'utilisons thisdans un lambda, nous aurons accès à l'objet de la classe framing. Où nous avons réellement écrit cette expression. Cela se produit parce que les expressions lambda, une fois compilées, deviennent 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 qui contredit les principes de la programmation fonctionnelle. Mais cette approche est tout à fait cohérente avec la POO. ;)

D'où ai-je obtenu l'information ou quoi d'autre lire

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION