1. Interfaces

Pour comprendre ce que sont les fonctions lambda, vous devez d'abord comprendre ce que sont les interfaces. Rappelons donc les principaux points.

Une interface est une variante du concept de classe. Une classe fortement tronquée, disons. Contrairement à une classe, une interface ne peut pas avoir ses propres variables (sauf celles statiques). Vous ne pouvez pas non plus créer d'objets dont le type est une interface :

  • Vous ne pouvez pas déclarer les variables de la classe
  • Vous ne pouvez pas créer d'objets

Exemple:

interface Runnable
{
   void run();
}
Exemple d'interface standard

Utiliser une interface

Alors pourquoi une interface est-elle nécessaire ? Les interfaces ne sont utilisées qu'avec l'héritage. La même interface peut être héritée par différentes classes, ou comme on dit aussi — les classes implémentent l' interface .

Si une classe implémente une interface, elle doit implémenter les méthodes déclarées par mais non implémentées par l'interface. Exemple:

interface Runnable
{
   void run();
}

class Timer implements Runnable
{
   void run()
   {
      System.out.println(LocalTime.now());
   }
}

class Calendar implements Runnable
{
   void run()
   {
      var date = LocalDate.now();
      System.out.println("Today: " + date.getDayOfWeek());
   }
}

La Timerclasse implémente l' Runnableinterface, elle doit donc déclarer en elle-même toutes les méthodes qui sont dans l' Runnableinterface et les implémenter, c'est-à-dire écrire du code dans un corps de méthode. Il en va de même pour la Calendarclasse.

Mais maintenant, Runnableles variables peuvent stocker des références à des objets qui implémentent l' Runnableinterface.

Exemple:

Code Note
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

La run()méthode de la Timerclasse sera appelée


La run()méthode de la Timerclasse sera appelée


La run()méthode de la Calendarclasse sera appelée

Vous pouvez toujours affecter une référence d'objet à une variable de n'importe quel type, tant que ce type est l'une des classes ancêtres de l'objet. Pour les classes Timeret Calendar, il existe deux types de ce type : Objectet Runnable.

Si vous affectez une référence d'objet à une Objectvariable, vous ne pouvez appeler que les méthodes déclarées dans la Objectclasse. Et si vous affectez une référence d'objet à une Runnablevariable, vous pouvez appeler les méthodes du Runnabletype.

Exemple 2 :

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

Ce code fonctionnera, car les objets Timeret Calendaront des méthodes d'exécution qui fonctionnent parfaitement bien. Donc, les appeler n'est pas un problème. Si nous venions d'ajouter une méthode run() aux deux classes, nous ne pourrions pas les appeler de manière aussi simple.

Fondamentalement, l' Runnableinterface n'est utilisée que comme emplacement pour mettre la méthode run.



2. Tri

Passons à quelque chose de plus pratique. Par exemple, regardons le tri des chaînes.

Pour trier une collection de chaînes par ordre alphabétique, Java a une excellente méthode appeléeCollections.sort(collection);

Cette méthode statique trie la collection transmise. Et dans le processus de tri, il effectue des comparaisons par paires de ses éléments afin de comprendre si les éléments doivent être échangés.

Lors du tri, ces comparaisons sont effectuées à l'aide de la compareTométhode (), que possèdent toutes les classes standard : Integer, String, ...

La méthode compareTo() de la classe Integer compare les valeurs de deux nombres, tandis que la méthode compareTo() de la classe String examine l'ordre alphabétique des chaînes.

Ainsi, une collection de nombres sera triée par ordre croissant, tandis qu'une collection de chaînes sera triée par ordre alphabétique.

Algorithmes de tri alternatifs

Mais que se passe-t-il si nous voulons trier les chaînes non pas par ordre alphabétique, mais par leur longueur ? Et si nous voulons trier les nombres par ordre décroissant ? Que faites-vous dans ce cas ?

Pour gérer de telles situations, la Collectionsclasse a une autre sort()méthode qui a deux paramètres :

Collections.sort(collection, comparator);

comparator est un objet spécial qui sait comment comparer les objets d'une collection lors d'une opération de tri . Le terme comparateur vient du mot anglais comparator , qui à son tour dérive de compare , signifiant "comparer".

Quel est donc cet objet spécial ?

Comparatorinterface

Eh bien, tout est très simple. Le type du sort()deuxième paramètre de la méthode estComparator<T>

Où T est un paramètre de type qui indique le type des éléments de la collection et Comparatorest une interface qui a une seule méthodeint compare(T obj1, T obj2);

En d'autres termes, un objet comparateur est n'importe quel objet de n'importe quelle classe qui implémente l'interface Comparator. L'interface du comparateur a l'air très simple :

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Code pour l'interface du comparateur

La compare()méthode compare les deux arguments qui lui sont passés.

Si la méthode renvoie un nombre négatif, cela signifie obj1 < obj2. Si la méthode renvoie un nombre positif, cela signifie obj1 > obj2. Si la méthode renvoie 0, cela signifie obj1 == obj2.

Voici un exemple d'objet comparateur qui compare les chaînes en fonction de leur longueur :

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Code de la StringLengthComparatorclasse

Pour comparer les longueurs de chaîne, soustrayez simplement une longueur de l'autre.

Le code complet d'un programme qui trie les chaînes par longueur ressemblerait à ceci :

public class Solution
{
   public static void main(String[] args)
   {
      ArrayList<String> list = new ArrayList<String>();
      Collections.addAll(list, "Hello", "how's", "life?");
      Collections.sort(list, new StringLengthComparator());
   }
}

class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Trier les chaînes par longueur


3. Sucre syntaxique

Que pensez-vous, ce code peut-il être écrit de manière plus compacte? Fondamentalement, il n'y a qu'une seule ligne qui contient des informations utiles - obj1.length() - obj2.length();.

Mais le code ne peut pas exister en dehors d'une méthode, nous avons donc dû ajouter une compare()méthode, et pour stocker la méthode, nous avons dû ajouter une nouvelle classe — StringLengthComparator. Et il faut aussi préciser les types des variables... Tout semble correct.

Mais il existe des moyens de raccourcir ce code. Nous avons du sucre syntaxique pour vous. Deux boules !

Classe intérieure anonyme

Vous pouvez écrire le code du comparateur directement dans la main()méthode, et le compilateur fera le reste. Exemple:

public class Solution
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list, "Hello", "how's", "life?");

        Comparator<String> comparator = new Comparator<String>()
        {
            public int compare (String obj1, String obj2)
            {
                return obj1.length() – obj2.length();
            }
        };

        Collections.sort(list, comparator);
    }
}
Trier les chaînes par longueur

Vous pouvez créer un objet qui implémente l' Comparatorinterface sans créer explicitement une classe ! Le compilateur le créera automatiquement et lui donnera un nom temporaire. Comparons:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Classe intérieure anonyme
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparatorclasse

La même couleur est utilisée pour indiquer des blocs de code identiques dans les deux cas différents. Les différences sont assez faibles dans la pratique.

Lorsque le compilateur rencontre le premier bloc de code, il génère simplement un deuxième bloc de code correspondant et donne à la classe un nom aléatoire.


4. Expressions lambda en Java

Supposons que vous décidiez d'utiliser une classe interne anonyme dans votre code. Dans ce cas, vous aurez un bloc de code comme celui-ci :

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Classe intérieure anonyme

Ici, nous combinons la déclaration d'une variable avec la création d'une classe anonyme. Mais il existe un moyen de raccourcir ce code. Par exemple, comme ceci :

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length() – obj2.length();
};

Le point-virgule est nécessaire car ici nous avons non seulement une déclaration de classe implicite, mais aussi la création d'une variable.

Une telle notation s'appelle une expression lambda.

Si le compilateur rencontre une telle notation dans votre code, il génère simplement la version détaillée du code (avec une classe interne anonyme).

Notez que lors de l'écriture de l'expression lambda, nous avons omis non seulement le nom de la classe, mais également le nom de la méthode.Comparator<String>int compare()

La compilation n'aura aucun problème à déterminer la méthode , car une expression lambda ne peut être écrite que pour les interfaces qui ont une seule méthode . Soit dit en passant, il existe un moyen de contourner cette règle, mais vous l'apprendrez lorsque vous commencerez à étudier la POO plus en profondeur (nous parlons des méthodes par défaut).

Examinons à nouveau la version détaillée du code, mais nous griserons la partie qui peut être omise lors de l'écriture d'une expression lambda :

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
};
Classe intérieure anonyme

Il semble que rien d'important n'ait été omis. En effet, si l' Comparatorinterface ne possède qu'une seule compare()méthode, le compilateur peut entièrement récupérer le code grisé du code restant.

Tri

Au fait, nous pouvons maintenant écrire le code de tri comme ceci :

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);

Ou même comme ça :

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length() – obj2.length();
   }
);

Nous avons simplement immédiatement remplacé la comparatorvariable par la valeur qui a été attribuée à la comparatorvariable.

Inférence de type

Mais ce n'est pas tout. Le code de ces exemples peut être écrit de manière encore plus compacte. Tout d'abord, le compilateur peut déterminer par lui-même que les variables obj1et obj2sont Strings. Et deuxièmement, les accolades et l'instruction de retour peuvent également être omises si vous n'avez qu'une seule commande dans le code de la méthode.

La version abrégée ressemblerait à ceci :

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length() – obj2.length();

Collections.sort(list, comparator);

Et si au lieu d'utiliser la comparatorvariable, on utilise immédiatement sa valeur, alors on obtient la version suivante :

Collections.sort(list, (obj1, obj2) ->  obj1.length() — obj2.length() );

Eh bien, qu'en pensez-vous? Une seule ligne de code sans informations superflues, uniquement des variables et du code. Il n'y a aucun moyen de le raccourcir! Ou est-il?



5. Comment ça marche

En fait, le code peut être écrit de manière encore plus compacte. Mais plus là-dessus plus tard.

Vous pouvez écrire une expression lambda dans laquelle vous utiliseriez un type d'interface avec une seule méthode.

Par exemple, dans le code , vous pouvez écrire une expression lambda car la signature de la méthode ressemble à ceci :Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

Lorsque nous avons passé la ArrayList<String>collection comme premier argument à la méthode sort, le compilateur a pu déterminer que le type du second argument est . Et à partir de là, il a conclu que cette interface a une seule méthode. Tout le reste est une technicité.Comparator<String>int compare(String obj1, String obj2)