Java Language
Expressions lambda
Recherche…
Introduction
Les expressions Lambda offrent un moyen clair et concis d'implémenter une interface à méthode unique utilisant une expression. Ils vous permettent de réduire la quantité de code à créer et à gérer. Bien que similaires aux classes anonymes, elles ne contiennent aucune information de type. La déduction de type doit avoir lieu.
Les références de méthode implémentent des interfaces fonctionnelles en utilisant des méthodes existantes plutôt que des expressions. Ils appartiennent également à la famille lambda.
Syntaxe
- () -> {expression de retour; } // Zéro-arité avec le corps de la fonction pour renvoyer une valeur.
- () -> expression // Abréviation de la déclaration ci-dessus; il n'y a pas de point-virgule pour les expressions.
- () -> {function-body} // Effet secondaire dans l'expression lambda pour effectuer des opérations.
- parameterName -> expression // expression lambda One-arity. Dans les expressions lambda avec un seul argument, la parenthèse peut être supprimée.
- (Tapez parameterName, tapez secondParameterName, ...) -> expression // lambda évaluant une expression avec les paramètres listés à gauche
- (parameterName, secondParameterName, ...) -> expression // Abréviation supprimant les types de paramètre pour les noms de paramètre. Ne peut être utilisé que dans des contextes pouvant être déduits par le compilateur où la taille de la liste de paramètres donnée correspond à un (et un seul) de la taille des interfaces fonctionnelles attendues.
Utilisation des expressions lambda pour trier une collection
Tri des listes
Avant Java 8, il était nécessaire d'implémenter l'interface java.util.Comparator
avec une classe anonyme (ou nommée) lors du tri d'une liste 1 :
List<Person> people = ...
Collections.sort(
people,
new Comparator<Person>() {
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
);
À partir de Java 8, la classe anonyme peut être remplacée par une expression lambda. Notez que les types pour les paramètres p1
et p2
peuvent être omis, comme le compilateur les inférera automatiquement:
Collections.sort(
people,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);
L'exemple peut être simplifié en utilisant Comparator.comparing
et les références de méthode exprimées en utilisant le symbole ::
(deux points).
Collections.sort(
people,
Comparator.comparing(Person::getFirstName)
);
Une importation statique nous permet de l'exprimer de manière plus concise, mais on peut se demander si cela améliore la lisibilité globale:
import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));
Les comparateurs construits de cette manière peuvent également être enchaînés. Par exemple, après avoir comparé des personnes par leur prénom, s'il y a des personnes avec le même prénom, la méthode thenComparing
avec également comparer par nom de famille:
sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));
1 - Notez que Collections.sort (...) ne fonctionne que sur les collections qui sont des sous-types de List
. Les API Set
et Collection
n'impliquent aucun classement des éléments.
Tri des cartes
Vous pouvez trier les entrées d'un HashMap
par valeur de la même manière. (Notez qu'un LinkedHashMap
doit être utilisé comme cible. Les clés d'un HashMap
ordinaire HashMap
sont pas ordonnées.)
Map<String, Integer> map = new HashMap(); // ... or any other Map class
// populate the map
map = map.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
(k, v) -> k, LinkedHashMap::new));
Introduction aux Java Lambda
Interfaces fonctionnelles
Lambdas ne peut fonctionner que sur une interface fonctionnelle, qui est une interface avec une seule méthode abstraite. Les interfaces fonctionnelles peuvent avoir un nombre quelconque de méthodes default
ou static
. (Pour cette raison, ils sont parfois appelés interfaces de méthode abstraite unique ou interfaces SAM).
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
Lors de la déclaration d'une interface fonctionnelle, l'annotation @FunctionalInterface
peut être ajoutée. Cela n'a pas d'effet particulier, mais une erreur de compilation sera générée si cette annotation est appliquée à une interface qui n'est pas fonctionnelle, rappelant ainsi que l'interface ne doit pas être modifiée.
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
À l'inverse, il ne s'agit pas d' une interface fonctionnelle, car il existe plusieurs méthodes abstraites :
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one should
// be considered as lambda?
}
Ce n’est pas non plus une interface fonctionnelle, car elle n’a pas de méthode:
interface BlankFoo2 { }
Prenez note de ce qui suit. Supposons que vous ayez
interface Parent { public int parentMethod(); }
et
interface Child extends Parent { public int ChildMethod(); }
Ensuite, Child
ne peut pas être une interface fonctionnelle car il dispose de deux méthodes spécifiées.
Java 8 fournit également un certain nombre d'interfaces fonctionnelles basées sur des modèles génériques dans le package java.util.function
. Par exemple, l'interface intégrée Predicate<T>
encapsule une méthode unique qui entre une valeur de type T
et retourne un boolean
.
Expressions lambda
La structure de base d'une expression Lambda est la suivante:
fi
contiendra alors une instance singleton d'une classe, similaire à une classe anonyme, qui implémente FunctionalInterface
et où la définition de la méthode est { System.out.println("Hello"); }
. En d'autres termes, ce qui précède équivaut principalement à:
FunctionalInterface fi = new FunctionalInterface() {
@Override
public void theOneMethod() {
System.out.println("Hello");
}
};
Le lambda est seulement "principalement équivalent" à la classe anonyme car dans un lambda, la signification des expressions comme this
, super
ou toString()
référence à la classe dans laquelle l'assignation a lieu, pas à l'objet nouvellement créé.
Vous ne pouvez pas spécifier le nom de la méthode lorsque vous utilisez un lambda, mais vous ne devriez pas en avoir besoin, car une interface fonctionnelle ne doit avoir qu'une seule méthode abstraite, de sorte que Java remplace celle-ci.
Dans les cas où le type de lambda n'est pas certain (par exemple, des méthodes surchargées), vous pouvez ajouter une distribution au lambda pour indiquer au compilateur quel devrait être son type, comme ceci:
Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true
Si la méthode unique de l'interface fonctionnelle prend des paramètres, les noms formels locaux de ceux-ci doivent apparaître entre les crochets du lambda. Il n'est pas nécessaire de déclarer le type du paramètre ou de le renvoyer car ceux-ci sont extraits de l'interface (bien que la déclaration des types de paramètres ne soit pas une erreur si vous le souhaitez). Ainsi, ces deux exemples sont équivalents:
Foo2 longFoo = new Foo2() {
@Override
public int bar(boolean baz) {
return baz ? 1 : 0;
}
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };
Les parenthèses autour de l'argument peuvent être omises si la fonction n'a qu'un seul argument:
Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay
Retours implicites
Si le code placé dans un lambda est une expression Java plutôt qu'une instruction , il est traité comme une méthode qui renvoie la valeur de l'expression. Ainsi, les deux suivants sont équivalents:
IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };
Accès aux variables locales (fermetures de valeur)
Comme les lambda sont des raccourcis syntaxiques pour les classes anonymes, elles suivent les mêmes règles pour accéder aux variables locales dans la portée englobante; les variables doivent être traitées comme final
et non modifiées à l'intérieur du lambda.
IntUnaryOperator makeAdder(int amount) {
return (x) -> (x + amount); // Legal even though amount will go out of scope
// because amount is not modified
}
IntUnaryOperator makeAccumulator(int value) {
return (x) -> { value += x; return value; }; // Will not compile
}
Si vous devez envelopper une variable de cette manière, vous devez utiliser un objet régulier qui conserve une copie de la variable. En savoir plus dans Java Closures avec des expressions lambda.
Accepter les Lambdas
Parce qu'un lambda est une implémentation d'une interface, il n'y a rien à faire pour qu'une méthode accepte un lambda: toute fonction qui prend une interface fonctionnelle peut aussi accepter un lambda.
public void passMeALambda(Foo1 f) {
f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));
Le type d'une expression lambda
Une expression lambda, en elle-même, n'a pas de type spécifique. S'il est vrai que les types et le nombre de paramètres, ainsi que le type d'une valeur de retour, peuvent véhiculer des informations de type, ces informations limiteront uniquement les types auxquels elles peuvent être affectées. Le lambda reçoit un type lorsqu'il est affecté à un type d'interface fonctionnelle de l'une des manières suivantes:
- Affectation directe à un type fonctionnel, par exemple
myPredicate = s -> s.isEmpty()
- En le passant comme paramètre ayant un type fonctionnel, par exemple
stream.filter(s -> s.isEmpty())
- Le
return s -> s.isEmpty()
depuis une fonction qui retourne un type fonctionnel, par exemplereturn s -> s.isEmpty()
- Le convertir en un type fonctionnel, par exemple
(Predicate<String>) s -> s.isEmpty()
Jusqu'à ce qu'une telle affectation à un type fonctionnel soit faite, le lambda n'a pas de type défini. Pour illustrer, considérons l'expression lambda o -> o.isEmpty()
. La même expression lambda peut être affectée à de nombreux types fonctionnels différents:
Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();
Maintenant qu'ils sont attribués, les exemples présentés sont de types complètement différents, même si les expressions lambda se ressemblent et qu'ils ne peuvent pas être affectés les uns aux autres.
Références de méthode
Les références de méthode permettent aux méthodes statiques ou d'instance prédéfinies qui adhèrent à une interface fonctionnelle compatible d'être transmises en tant qu'argument au lieu d'une expression lambda anonyme.
Supposons que nous ayons un modèle:
class Person {
private final String name;
private final String surname;
public Person(String name, String surname){
this.name = name;
this.surname = surname;
}
public String getName(){ return name; }
public String getSurname(){ return surname; }
}
List<Person> people = getSomePeople();
Référence de la méthode d'instance (à une instance arbitraire)
people.stream().map(Person::getName)
Le lambda équivalent:
people.stream().map(person -> person.getName())
Dans cet exemple, une référence de méthode à la méthode d'instance getName()
de type Person
est en cours de transmission. Comme il est connu pour être du type collection, la méthode de l'instance (connue plus tard) sera appelée.
Référence de la méthode d'instance (à une instance spécifique)
people.forEach(System.out::println);
Puisque System.out
est une instance de PrintStream
, une référence de méthode à cette instance spécifique est transmise en tant qu'argument.
Le lambda équivalent:
people.forEach(person -> System.out.println(person));
Référence de méthode statique
Aussi, pour transformer des flux, nous pouvons appliquer des références à des méthodes statiques:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)
Cet exemple transmet une référence à la méthode static valueOf()
sur le type String
. Par conséquent, l'objet instance dans la collection est transmis en tant qu'argument à valueOf()
.
Le lambda équivalent:
numbers.stream().map(num -> String.valueOf(num))
Référence à un constructeur
List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)
Lire Collecter les éléments d'un flux dans une collection pour voir comment collecter les éléments à collecter.
Le seul constructeur d'argument String du type Integer
est utilisé ici pour construire un entier à partir de la chaîne fournie comme argument. Dans ce cas, tant que la chaîne représente un nombre, le flux sera mappé aux nombres entiers. Le lambda équivalent:
strings.stream().map(s -> new Integer(s));
Cheat-Sheet
Format de référence de la méthode | Code | Lambda équivalent |
---|---|---|
Méthode statique | TypeName::method | (args) -> TypeName.method(args) |
Méthode non statique (sur instance * ) | instance::method | (args) -> instance.method(args) |
Méthode non statique (pas d'instance) | TypeName::method | (instance, args) -> instance.method(args) |
Constructeur ** | TypeName::new | (args) -> new TypeName(args) |
Constructeur tableau | TypeName[]::new | (int size) -> new TypeName[size] |
* instance
peut être n'importe quelle expression qui donne une référence à une instance, par exemple getInstance()::method
, this::method
** Si TypeName
est une classe interne non statique, la référence du constructeur est uniquement valide dans le cadre d'une instance de classe externe
Implémentation de plusieurs interfaces
Parfois, vous souhaiterez peut-être avoir une expression lambda implémentant plusieurs interfaces. Ceci est surtout utile avec les interfaces de marqueurs (telles que java.io.Serializable ) car elles n’ajoutent pas de méthodes abstraites.
Par exemple, vous voulez créer un TreeSet
avec un Comparator
personnalisé, puis le sérialiser et l'envoyer sur le réseau. L'approche triviale:
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
ne fonctionne pas car le lambda pour le comparateur n'implémente pas Serializable
. Vous pouvez résoudre ce problème en utilisant des types d'intersection et en spécifiant explicitement que ce lambda doit être sérialisable:
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
Si vous utilisez fréquemment des types d'intersection (par exemple, si vous utilisez un framework tel qu'Apache Spark où presque tout doit être sérialisable), vous pouvez créer des interfaces vides et les utiliser à la place dans votre code:
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
De cette façon, vous êtes assuré que le comparateur passé sera sérialisable.
Lambdas et motif d'exécutions
Il existe plusieurs bons exemples d'utilisation de lambdas en tant qu'interface fonctionnelle dans des scénarios simples. Un cas d'utilisation assez courant qui peut être amélioré par lambdas est ce qu'on appelle le modèle Execute-Around. Dans ce modèle, vous disposez d'un ensemble de codes d'installation / de suppression standard nécessaires pour plusieurs scénarios entourant un code spécifique à un cas d'utilisation. Un exemple typique de ceci est le fichier io, la base de données io, les blocs try / catch.
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
Alors, appeler cette méthode avec un lambda pourrait ressembler à ceci:
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
Ceci n'est pas limité aux opérations d'E / S. Il peut s’appliquer à tout scénario dans lequel des tâches similaires de configuration / élimination sont applicables avec des variations mineures. Le principal avantage de ce modèle est la réutilisation du code et l’application de DRY (ne vous répète pas).
Utiliser l'expression lambda avec votre propre interface fonctionnelle
Les Lambdas sont censés fournir un code d'implémentation en ligne pour les interfaces de méthode unique et la possibilité de les faire circuler comme nous l'avons fait avec les variables normales. Nous les appelons Interface fonctionnelle.
Par exemple, écrire un objet Runnable dans une classe anonyme et démarrer un thread ressemble à ceci:
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
Maintenant, comme ci-dessus, disons que vous avez une interface personnalisée:
interface TwoArgInterface {
int operate(int a, int b);
}
Comment utilisez-vous lambda pour implémenter cette interface dans votre code? Identique à l'exemple Runnable ci-dessus. Voir le programme de pilote ci-dessous:
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}
`return` ne renvoie que de la méthode lambda, pas de la méthode externe
La méthode return
ne renvoie que de la méthode lambda et non de la méthode externe.
Attention, c'est différent de Scala et Kotlin!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
Cela peut conduire à un comportement inattendu lors de la tentative d'écriture de constructions de langage propres, car dans les constructions intégrées telles que for
boucles, le return
se comporte différemment:
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
Dans Scala et Kotlin, la demo
et la demo2
que 0
. Mais ce n'est pas plus cohérent. L’approche Java est compatible avec le refactoring et l’utilisation de classes - le return
dans le code en haut et le code ci-dessous se comporte de la même façon:
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
Par conséquent, le Java return
est plus compatible avec les méthodes de classe et refactoring, mais moins avec le for
et while
builtins, ceux - ci restent particulièrement.
De ce fait, les deux suivants sont équivalents en Java:
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
De plus, l'utilisation de try-with-resources est sûre en Java:
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
imprimera before accept() after close()
. Dans la sémantique de Scala et Kotlin, les essais avec ressources ne seraient pas fermés, mais ils imprimeraient before accept()
seulement.
Fermetures Java avec des expressions lambda.
Une fermeture lambda est créée lorsqu'une expression lambda référence les variables d'une étendue englobante (globale ou locale). Les règles pour cela sont les mêmes que pour les méthodes en ligne et les classes anonymes.
Les variables locales d'une étendue englobante utilisées dans un lambda doivent être final
. Avec Java 8 (la première version prenant en charge lambdas), il n'est pas nécessaire de les déclarer final
dans le contexte extérieur, mais elles doivent être traitées de cette manière. Par exemple:
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
Ceci est légal tant que la valeur de la variable n
n'est pas modifiée. Si vous essayez de changer la variable, à l'intérieur ou à l'extérieur du lambda, vous obtiendrez l'erreur de compilation suivante:
"Les variables locales référencées à partir d'une expression lambda doivent être finales ou effectivement finales ".
Par exemple:
int n = 0;
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n++; // Will generate an error.
S'il est nécessaire d'utiliser une variable variable dans un lambda, l'approche normale consiste à déclarer une copie final
de la variable et à utiliser la copie. Par exemple
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
Naturellement, le corps du lambda ne voit pas les modifications apportées à la variable d'origine.
Notez que Java ne prend pas en charge les fermetures réelles. Un lambda Java ne peut pas être créé d'une manière qui lui permet de voir les changements dans l'environnement dans lequel il a été instancié. Si vous souhaitez implémenter une fermeture qui observe ou modifie son environnement, vous devez la simuler en utilisant une classe régulière. Par exemple:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
L'exemple ci-dessus ne sera pas compilé pour les raisons évoquées précédemment. Nous pouvons contourner l'erreur de compilation comme suit:
// Compiles, but is incorrect ...
public class AccumulatorGenerator {
private int value = 0;
public IntUnaryOperator createAccumulator() {
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
}
Le problème est que cela rompt le contrat de conception de l'interface IntUnaryOperator
qui stipule que les instances doivent être fonctionnelles et sans état. Si une telle fermeture est transmise à des fonctions intégrées acceptant des objets fonctionnels, elle est susceptible de provoquer des pannes ou un comportement erroné. Les fermetures qui encapsulent un état mutable doivent être implémentées comme des classes régulières. Par exemple.
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}
Lambda - Exemple d'écoute
Auditeur de classe anonyme
Avant Java 8, il est très courant qu'une classe anonyme soit utilisée pour gérer l'événement click d'un JButton, comme indiqué dans le code suivant. Cet exemple montre comment implémenter un écouteur anonyme dans la portée de btn.addActionListener
.
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
Auditeur lambda
Étant donné que l'interface ActionListener
ne définit qu'une méthode actionPerformed()
, il s'agit d'une interface fonctionnelle, ce qui signifie qu'il est possible d'utiliser des expressions Lambda pour remplacer le code standard. L'exemple ci-dessus peut être réécrit en utilisant les expressions Lambda comme suit:
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
Style traditionnel au style Lambda
Façon traditionnelle
interface MathOperation{
boolean unaryOperation(int num);
}
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = new MathOperation() {
@Override
public boolean unaryOperation(int num) {
return num%2 == 0;
}
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
Style Lambda
- Supprimez le nom de la classe et le corps de l'interface fonctionnelle.
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = (int num) -> {
return num%2 == 0;
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
- Déclaration de type facultative
MathOperation isEven = (num) -> {
return num%2 == 0;
};
- Parenthèse optionnelle autour du paramètre, s'il s'agit d'un paramètre unique
MathOperation isEven = num -> {
return num%2 == 0;
};
- Accolades optionnelles, s'il n'y a qu'une seule ligne dans le corps de la fonction
- Mot clé de retour facultatif, s'il n'y a qu'une seule ligne dans le corps de la fonction
MathOperation isEven = num -> num%2 == 0;
Lambdas et utilisation de la mémoire
Comme les lambdas Java sont des fermetures, ils peuvent "capturer" les valeurs des variables dans la portée lexicale. Bien que tous les lambda ne capturent rien - de simples lambda comme s -> s.length()
ne capturent rien et s'appellent sans état - la capture de lambdas nécessite un objet temporaire pour contenir les variables capturées. Dans cet extrait de code, le lambda () -> j
est un lambda de capture et peut provoquer l’allocation d’un objet lorsqu’il est évalué:
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000000; i++) {
int j = i;
doSomethingWithLambda(() -> j);
}
}
Bien que cela ne soit pas immédiatement évident puisque le new
mot-clé n'apparaît nulle part dans le fragment, ce code est susceptible de créer 1 000 000 000 d'objets séparés pour représenter les instances de l'expression () -> j
lambda. Cependant, il convient également de noter que les futures versions de Java 1 pourraient être en mesure de l’optimiser, de sorte que lors de l’exécution, les instances lambda soient réutilisées ou représentées d’une autre manière.
1 - Par exemple, Java 9 introduit une phase facultative de "lien" à la séquence de compilation Java, ce qui permettra de réaliser de telles optimisations globales.
Utilisation d'expressions lambda et de prédicats pour obtenir une ou plusieurs valeurs dans une liste
À partir de Java 8, vous pouvez utiliser des expressions et des prédicats lambda.
Exemple: Utilisez des expressions lambda et un prédicat pour obtenir une certaine valeur dans une liste. Dans cet exemple, chaque personne sera imprimée avec le fait qu'elle ait 18 ans ou plus.
Classe de personne:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
}
L'interface intégrée Predicate des packages java.util.function.Predicate est une interface fonctionnelle avec une méthode de boolean test(T t)
.
Exemple d'utilisation:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Jeroen", 20));
personList.add(new Person("Jack", 5));
personList.add(new Person("Lisa", 19));
print(personList, p -> p.getAge() >= 18);
}
private static void print(List<Person> personList, Predicate<Person> checker) {
for (Person person : personList) {
if (checker.test(person)) {
System.out.print(person + " matches your expression.");
} else {
System.out.println(person + " doesn't match your expression.");
}
}
}
}
Le print(personList, p -> p.getAge() >= 18);
méthode prend une expression lambda (parce que le prédicat est utilisé un paramètre) où vous pouvez définir l'expression nécessaire. La méthode de test du vérificateur vérifie si cette expression est correcte ou non: checker.test(person)
.
Vous pouvez facilement changer cela pour quelque chose d'autre, par exemple pour print(personList, p -> p.getName().startsWith("J"));
. Cela vérifiera si le nom de la personne commence par un "J".