Comment utiliser les références de méthodes en Java?

Les références de méthodes sont une autre façon de rendre le code plus lisible, comme simplement mentionner le nom de la méthode. Comme les lambdas, il faut du temps pour s’habituer à la nouvelle syntaxe. Dans cette section, nous montrons la syntaxe ainsi que les quatre types de références de méthodes. Nous mélangeons également les lambdas avec les références de méthodes.

Supposons que nous codons un caneton qui essaie d’apprendre à cancaner. D’abord, nous avons une interface fonctionnelle :

public interface ApprendreAParler {
    void parler(String son);
}

Ensuite, nous découvrons que notre caneton a de la chance. Il existe une classe auxiliaire avec laquelle le caneton peut travailler. Nous avons omis les détails de l’enseignement au caneton comment cancaner et laissé la partie qui appelle l’interface fonctionnelle :

public class AideCaneton {
    public static void professeur(String nom, ApprendreAParler formateur) {
        // Exercer la patience (omis)
        formateur.parler(nom);
    }
}

Enfin, il est temps de tout assembler et de rencontrer notre petit Caneton. Ce code implémente l’interface fonctionnelle en utilisant un lambda :

public class Caneton {
    public static void faireSon(String son) {
        ApprendreAParler apprenant = s -> System.out.println(s);
        AideCaneton.professeur(son, apprenant);
    }
}

Pas mal. Il y a un peu de redondance, cependant. Le lambda déclare un paramètre nommé s. Cependant, il ne fait rien d’autre que passer ce paramètre à une autre méthode. Une référence de méthode nous permet de supprimer cette redondance et d’écrire à la place :

ApprendreAParler apprenant = System.out::println;

L’opérateur :: indique à Java d’appeler la méthode println() plus tard. Il faudra un peu de temps pour s’habituer à la syntaxe. Une fois que vous le ferez, vous pourriez trouver que votre code est plus court et moins distrayant sans écrire autant de lambdas.

N’oubliez pas que :: est comme un lambda, et il est utilisé pour une exécution différée avec une interface fonctionnelle. Vous pouvez même imaginer la référence de méthode comme un lambda si cela vous aide.

Une référence de méthode et un lambda se comportent de la même manière à l’exécution. Vous pouvez imaginer que le compilateur transforme vos références de méthodes en lambdas pour vous.

Il existe quatre formats pour les références de méthodes :

  • Méthodes statiques
  • Méthodes d’instance sur un objet particulier
  • Méthodes d’instance sur un paramètre à déterminer lors de l’exécution
  • Constructeurs

Examinons brièvement chacun d’entre eux. Dans chaque exemple, nous montrons la référence de méthode et son équivalent lambda. Pour l’instant, nous créons une interface fonctionnelle distincte pour chaque exemple. Dans la section suivante, nous introduirons des interfaces fonctionnelles intégrées afin que vous n’ayez pas à continuer à écrire les vôtres.

Appel de méthodes statiques

Pour le premier exemple, nous utilisons une interface fonctionnelle qui convertit un double en long :

interface Convertisseur {
    long arrondir(double num);
}

Nous pouvons implémenter cette interface avec la méthode round() de Math. Ici, nous assignons une référence de méthode et un lambda à cette interface fonctionnelle :

Convertisseur refMethode = Math::round;
Convertisseur lambda = x -> Math.round(x);

System.out.println(refMethode.arrondir(100.1)); // 100

Nous référençons une méthode avec un paramètre, et Java sait que c’est comme un lambda avec un paramètre. De plus, Java sait qu’il faut passer ce paramètre à la méthode.

Attendez une minute. Vous pourriez savoir que la méthode round() est surchargée – elle peut prendre un double ou un float. Comment Java sait-il que nous voulons appeler la version avec un double ? Avec les lambdas et les références de méthodes, Java déduit des informations du contexte. Dans ce cas, nous avons dit que nous déclarions un Convertisseur, qui a une méthode prenant un paramètre double. Java cherche une méthode qui correspond à cette description. S’il ne peut pas la trouver ou trouve plusieurs correspondances, le compilateur signalera une erreur. Cette dernière est parfois appelée une erreur de type ambigu.

Appel de méthodes d’instance sur un objet particulier

Pour cet exemple, notre interface fonctionnelle vérifie si une chaîne commence par une valeur spécifiée :

interface DebutChaine {
    boolean verificationDebut(String prefixe);
}

Commodément, la classe String possède une méthode startsWith() qui prend un paramètre et renvoie un booléen. Voyons comment utiliser les références de méthodes avec ce code :

var str = "Zoo";
DebutChaine refMethode = str::startsWith;
DebutChaine lambda = s -> str.startsWith(s);

System.out.println(refMethode.verificationDebut("A")); // false

Cela montre que nous voulons appeler str.startsWith() et passer un seul paramètre à fournir lors de l’exécution. Ce serait une bonne façon de filtrer les données dans une liste.

Une référence de méthode n’a pas besoin de prendre des paramètres. Dans cet exemple, nous créons une interface fonctionnelle avec une méthode qui ne prend aucun paramètre mais renvoie une valeur :

interface VerificateurChaine {
    boolean verifier();
}

Nous l’implémentons en vérifiant si la chaîne est vide :

var str = "";
VerificateurChaine refMethode = str::isEmpty;
VerificateurChaine lambda = () -> str.isEmpty();

System.out.print(refMethode.verifier()); // true

Puisque la méthode sur String est une méthode d’instance, nous appelons la référence de méthode sur une instance de la classe String.

Bien que toutes les références de méthodes puissent être transformées en lambdas, l’inverse n’est pas toujours vrai. Par exemple, considérez ce code :

var str = "";
VerificateurChaine lambda = () -> str.startsWith("Zoo");

Comment pourrions-nous écrire cela comme une référence de méthode ? Vous pourriez essayer l’une des suivantes :

VerificateurChaine referenceMethode = str::startsWith; // NE COMPILE PAS
VerificateurChaine referenceMethode = str::startsWith("Zoo"); // NE COMPILE PAS

Aucune de ces options ne fonctionne ! Bien que nous puissions passer le str dans le cadre de la référence de méthode, il n’y a aucun moyen de passer le paramètre “Zoo” avec elle. Par conséquent, il n’est pas possible d’écrire ce lambda comme une référence de méthode.

Appel de méthodes d’instance sur un paramètre

Cette fois, nous allons appeler la même méthode d’instance qui ne prend aucun paramètre. L’astuce est que nous le ferons sans connaître l’instance à l’avance. Nous avons besoin d’une interface fonctionnelle différente cette fois car elle doit connaître la chaîne :

interface VerificateurParametreChaine {
    boolean verifier(String texte);
}

Nous pouvons implémenter cette interface fonctionnelle comme suit :

VerificateurParametreChaine refMethode = String::isEmpty;
VerificateurParametreChaine lambda = s -> s.isEmpty();

System.out.println(refMethode.verifier("Zoo")); // false

La méthode que nous voulons appeler est déclarée dans String. Cela ressemble à une méthode statique, mais ce n’est pas le cas. Au lieu de cela, Java sait que isEmpty() est une méthode d’instance qui ne prend aucun paramètre. Java utilise le paramètre fourni lors de l’exécution comme l’instance sur laquelle la méthode est appelée.

Comparez les références de méthode et lambda avec ceux de notre exemple d’instance. Ils se ressemblent, bien que l’un fasse référence à une variable locale nommée str, tandis que l’autre ne fait référence qu’aux paramètres de l’interface fonctionnelle.

Vous pouvez même combiner les deux types de références de méthodes d’instance. Encore une fois, nous avons besoin d’une nouvelle interface fonctionnelle qui prend deux paramètres :

interface VerificateurDeuxParametresChaine {
    boolean verifier(String texte, String prefixe);
}

Faites attention à l’ordre des paramètres lors de la lecture de l’implémentation :

VerificateurDeuxParametresChaine refMethode = String::startsWith;
VerificateurDeuxParametresChaine lambda = (s, p) -> s.startsWith(p);

System.out.println(refMethode.verifier("Zoo", "A")); // false

Puisque l’interface fonctionnelle prend deux paramètres, Java doit déterminer ce qu’ils représentent. Le premier sera toujours l’instance de l’objet pour les méthodes d’instance. Tous les autres sont les paramètres de la méthode.

N’oubliez pas que la référence de méthode peut ressembler à une méthode statique, mais c’est en réalité une référence de méthode déclarant que l’instance de l’objet sera spécifiée ultérieurement. Le lambda montre une partie de la puissance d’une référence de méthode. Nous avons pu remplacer deux paramètres lambda cette fois.

Appel de constructeurs

Une référence de constructeur est un type spécial de référence de méthode qui utilise new au lieu d’un nom de méthode et instancie un objet. Pour cet exemple, notre interface fonctionnelle ne prendra aucun paramètre mais retournera une chaîne :

interface CreateurChaineVide {
    String creer();
}

Pour l’appeler, nous utilisons new comme s’il s’agissait d’un nom de méthode :

CreateurChaineVide refMethode = String::new;
CreateurChaineVide lambda = () -> new String();

var maChaîne = refMethode.creer();
System.out.println(maChaîne.equals("Serpent")); // false

Cela s’étend comme les références de méthodes que vous avez vues jusqu’ici. Les références de méthodes peuvent être délicates. Cette fois, nous créons une interface fonctionnelle qui prend un paramètre et renvoie un résultat :

interface CopieurChaine {
    String copier(String valeur);
}

Dans l’implémentation, notez que la référence de méthode est la même que dans l’exemple précédent :

CopieurChaine refMethode = String::new;
CopieurChaine lambda = x -> new String(x);

var maChaîne = refMethode.copier("Zebre");
System.out.println(maChaîne.equals("Zebre")); // true

Cela signifie que vous ne pouvez pas toujours déterminer quelle méthode peut être appelée en regardant la référence de méthode. Au lieu de cela, vous devez examiner le contexte pour voir quels paramètres sont utilisés et s’il y a un type de retour. Dans cet exemple, Java voit que nous passons un paramètre String et appelle le constructeur de String qui prend un tel paramètre.

Révision des références de méthodes

La lecture des références de méthodes est utile pour comprendre le code. Le tableau ci-dessous montre les quatre types de références de méthodes. Si ce tableau n’a pas de sens, veuillez relire la section précédente. Il peut falloir quelques essais avant que les références de méthodes commencent à être logiques.

TypeAvant les deux-pointsAprès les deux-pointsExemple
Méthodes statiquesNom de la classeNom de la méthodeMath::random
Méthodes d’instance sur un objet particulierNom de la variable d’instanceNom de la méthodestr::startsWith
Méthodes d’instance sur un paramètreNom de la classeNom de la méthodeString::isEmpty
ConstructeurNom de la classenewString::new