Maintenant que nous avons appris les interfaces fonctionnelles, nous allons les utiliser pour montrer différentes approches pour les variables. Elles peuvent apparaître à trois endroits par rapport aux lambdas : la liste des paramètres, les variables locales déclarées à l’intérieur du corps de la lambda, et les variables référencées depuis le corps de la lambda. Nous allons explorer chacun d’entre eux pour que vous soyez alertes lorsque des pièges se présentent !
Interfaces fonctionnelles spécifiques aux types primitifs
Interfaces fonctionnelles | Type de retour | Méthode abstraite unique | Nombre de paramètres |
---|---|---|---|
ToDoubleFunction<T> ToIntFunction<T> ToLongFunction<T> | double int long | applyAsDouble applyAsInt applyAsLong | 1 (T) |
ToDoubleBiFunction<T, U> ToIntBiFunction<T, U> ToLongBiFunction<T, U> | double int long | applyAsDouble applyAsInt applyAsLong | 2 (T, U) |
DoubleToIntFunction DoubleToLongFunction IntToDoubleFunction IntToLongFunction LongToDoubleFunction LongToIntFunction | int long double long double int | applyAsInt applyAsLong applyAsDouble applyAsLong applyAsDouble applyAsInt | 1 (double) 1 (double) 1 (int) 1 (int) 1 (long) 1 (long) |
ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T> | void | accept | 2 (T, double) 2 (T, int) 2 (T, long) |
Lister les Paramètres
Plus tôt dans ce chapitre, vous avez appris que spécifier le type des paramètres est facultatif. De plus, var peut être utilisé à la place du type spécifique. Cela signifie que ces trois instructions sont interchangeables :
Predicate<String> p = x -> true;
Predicate<String> p = (var x) -> true;
Predicate<String> p = (String x) -> true;
On pourrait vous demander d’identifier le type du paramètre lambda. Dans notre exemple, la réponse est String. Comment avons-nous déterminé cela ? Une lambda déduit les types à partir du contexte environnant. Cela signifie que vous pouvez faire de même.
Dans ce cas, la lambda est assignée à un Predicate qui prend un String. Un autre endroit où chercher le type est dans la signature de méthode. Essayons un autre exemple. Pouvez-vous déterminer le type de x ?
public void quesuisje() {
consommer((var x) -> System.out.print(x), 123);
}
public void consommer(Consumer<Integer> c, int num) {
c.accept(num);
}
Si vous avez deviné Integer, vous aviez raison. La méthode quesuisje() crée une lambda à passer à la méthode consommer(). Puisque la méthode consommer() attend un Integer comme générique, nous savons que c’est le type inféré de x.
Mais attendez, il y a plus. Dans certains cas, vous pouvez déterminer le type sans même voir la signature de la méthode. Quel pensez-vous être le type de x ici ?
public void compter(List<Integer> liste) {
liste.sort((var x, var y) -> x.compareTo(y));
}
La réponse est encore Integer. Puisque nous trions une liste, nous pouvons utiliser le type de la liste pour déterminer le type du paramètre lambda.
Comme les paramètres lambda sont comme des paramètres de méthode, vous pouvez leur ajouter des modificateurs. Plus précisément, vous pouvez ajouter le modificateur final ou une annotation, comme dans cet exemple :
public void compter(List<Integer> liste) {
liste.sort((final var x, @Deprecated var y) -> x.compareTo(y));
}
Formats de Liste de Paramètres
Vous disposez de trois formats pour spécifier les types de paramètres dans une lambda : sans types, avec types et avec var. Le compilateur exige que tous les paramètres de la lambda utilisent le même format. Pouvez-vous voir pourquoi les éléments suivants ne sont pas valides ?
(var x, y) -> "Bonjour" // NE COMPILE PAS
(var x, Integer y) -> true // NE COMPILE PAS
(String x, var y, Integer z) -> true // NE COMPILE PAS
(Integer x, y) -> "au revoir" // NE COMPILE PAS
La ligne 1 a besoin de supprimer var de x ou de l’ajouter à y. Ensuite, les lignes 2 et 3 doivent utiliser le type ou var de manière cohérente. Enfin, la ligne 4 doit supprimer Integer de x ou ajouter un type à y.
Utiliser des Variables Locales à l’Intérieur d’un Corps de Lambda
Bien qu’il soit plus courant qu’un corps de lambda soit une seule expression, il est légal de définir un bloc. Ce bloc peut contenir tout ce qui est valide dans un bloc Java normal, y compris des déclarations de variables locales.
Le code suivant fait exactement cela. Il crée une variable locale nommée c qui est limitée au bloc lambda :
(a, b) -> { int c = 0; return 5; }
Maintenant, essayons un autre. Voyez-vous ce qui ne va pas ici ?
(a, b) -> { int a = 0; return 5; } // NE COMPILE PAS
Nous avons essayé de redéclarer a, ce qui n’est pas autorisé. Java ne vous permet pas de créer une variable locale avec le même nom qu’une déjà déclarée dans cette portée.
Maintenant, essayons un exemple difficile. Combien d’erreurs de syntaxe voyez-vous dans cette méthode ?
public void variables(int a) {
int b = 1;
Predicate<Integer> p1 = a -> {
int b = 0;
int c = 0;
return b == c; }
}
Il y a trois erreurs de syntaxe. La première est à la ligne qui commence par “Predicate”. La variable a a déjà été utilisée dans cette portée comme paramètre de méthode, donc elle ne peut pas être réutilisée. L’erreur de syntaxe suivante vient à la ligne qui commence par “int b”, où le code tente de redéclarer la variable locale b. La troisième erreur de syntaxe est très subtile et sur la ligne commençant par “return”. La variable p1 manque un point-virgule à la fin. Il y a un point-virgule avant le }, mais c’est à l’intérieur du bloc.
Monde Réel
Gardez Vos Lambdas Courtes
Avoir une lambda avec plusieurs lignes et une instruction return est souvent un indice que vous devriez refactoriser et mettre ce code dans une méthode. Par exemple, l’exemple précédent pourrait être réécrit comme :
Predicate<Integer> p1 = a -> retournerPareil(a);
Cette forme plus simple peut être davantage refactorisée pour utiliser une référence de méthode :
Predicate<Integer> p1 = this::retournerPareil;
Vous pourriez vous demander pourquoi c’est si important. Au chapitre 10, les lambdas et les références de méthodes sont utilisées dans des appels de méthodes chaînés. Plus la lambda est courte, plus il est facile de lire le code.
Référencer des Variables depuis le Corps de la Lambda
Les corps de lambda sont autorisés à référencer certaines variables du code environnant. Le code suivant est légal :
public class Corbeau {
private String couleur;
public void croasser(String nom) {
String volume = "fortement";
Consumer<String> consumer = s ->
System.out.println(nom + " dit "
+ volume + " qu'elle est " + couleur);
}
}
Cela montre qu’une lambda peut accéder à une variable d’instance, un paramètre de méthode ou une variable locale sous certaines conditions. Les variables d’instance (et les variables de classe) sont toujours autorisées.
La seule chose que les lambdas ne peuvent pas accéder sont les variables qui ne sont pas final ou effectivement final. Si vous avez besoin d’un rappel sur “effectivement final”, consultez le Chapitre 5, “Méthodes”.
C’est encore plus intéressant lorsque vous regardez où les erreurs de compilateur se produisent lorsque les variables ne sont pas effectivement final.
public class Corbeau {
private String couleur;
public void croasser(String nom) {
String volume = "fortement";
nom = "Caty";
couleur = "noir";
Consumer<String> consumer = s ->
System.out.println(nom + " dit " // NE COMPILE PAS
+ volume + " qu'elle est " + couleur); // NE COMPILE PAS
volume = "doucement";
}
}
Dans cet exemple, le paramètre de méthode nom n’est pas effectivement final car il est défini à la ligne commençant par “nom =”. Cependant, l’erreur de compilateur se produit à la ligne contenant “System.out.println”. Ce n’est pas un problème d’assigner une valeur à une variable non finale. Cependant, une fois que la lambda essaie de l’utiliser, nous avons un problème. La variable n’est plus effectivement finale, donc la lambda n’est pas autorisée à utiliser la variable.
La variable volume n’est pas effectivement finale non plus puisqu’elle est mise à jour à la ligne commençant par “volume =”. Dans ce cas, l’erreur de compilateur est à la ligne contenant “volume +”. C’est avant la réaffectation ! Encore une fois, l’acte d’assigner une valeur n’est un problème que du point de vue de la lambda. Par conséquent, la lambda doit être celle qui génère l’erreur de compilateur.
Pour réviser, assurez-vous d’avoir mémorisé ce tableau.
Type de variable | Règle |
---|---|
Variable d’instance | Autorisée |
Variable statique | Autorisée |
Variable locale | Autorisée si final ou effectivement final |
Paramètre de méthode | Autorisé si final ou effectivement final |
Paramètre lambda | Autorisé |