Introduction
Les expressions Lambda constituent une nouvelle fonctionnalité importante de Java SE 8. Les expressions Lambda vous permettent de remplacer les interfaces fonctionnelles par des expressions. Une expression lambda est comme une méthode, elle fournit une liste de paramètres normaux et un corps (qui peut être une expression ou un bloc de code) qui utilise ces paramètres.
Les expressions Lambda améliorent également la bibliothèque de collections. Java SE 8 ajoute deux packages pour les opérations par lots sur les données de collecte : le package java.util.function et le package java.util.stream. Un flux est comme un itérateur, mais avec de nombreuses fonctionnalités supplémentaires. Dans l'ensemble, les expressions et les flux lambda constituent les changements les plus importants depuis l'ajout de génériques et d'annotations au langage Java. Dans cet article, nous verrons la puissance des expressions et des flux lambda, des exemples simples aux exemples complexes.
Préparation de l'environnement
Si Java 8 n'a pas été installé, vous devez d'abord l'installer avant de pouvoir utiliser lambda et stream (le traducteur recommande de l'installer sur une machine virtuelle à des fins de test). Les outils et IDE tels que NetBeans et IntelliJ IDEA prennent en charge les fonctionnalités de Java 8, notamment les expressions lambda, les annotations répétables, les profils compacts et d'autres fonctionnalités.
Syntaxe des expressions lambda
Syntaxe de base :
(paramètres) -> expression
ou
(paramètres) ->{ déclarations }
Voici un exemple simple d'expression Java lambda :
Copiez le code comme suit :
// 1. Aucun paramètre n'est requis, la valeur de retour est 5
() -> 5
// 2. Reçoit un paramètre (type numérique) et renvoie 2 fois sa valeur
x -> 2*x
// 3. Acceptez 2 paramètres (nombres) et renvoyez leur différence
(x, y) -> xy
// 4. Reçoit 2 entiers de type int et renvoie leur somme
(int x, int y) -> x + y
// 5. Acceptez un objet chaîne et imprimez-le sur la console sans renvoyer aucune valeur (on dirait qu'il renvoie void)
(Chaîne s) -> System.out.print(s)
Exemple Lambda de base
Maintenant que nous savons ce que sont les expressions lambda, commençons par quelques exemples de base. Dans cette section, nous verrons comment les expressions lambda affectent la façon dont nous codons. Supposons qu'il existe une liste de joueurs, le programmeur peut utiliser une instruction for ("boucle for") pour parcourir, qui peut être convertie en une autre forme dans Java SE 8 :
Copiez le code comme suit :
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer", "Roger Federer",
"Andy Murray", "Tomas Berdych",
"Juan Martín Del Potro"} ;
List<String> joueurs = Arrays.asList(atp);
//Méthode de boucle précédente
pour (Lecteur de cordes : joueurs) {
System.out.print(joueur + "; ");
}
//Utiliser des expressions lambda et des opérations fonctionnelles
joueurs.forEach((joueur) -> System.out.print(joueur + "; "));
//Utiliser l'opérateur double deux-points dans Java 8
joueurs.forEach(System.out::println);
Comme vous pouvez le constater, les expressions lambda peuvent réduire notre code à une seule ligne. Un autre exemple concerne les programmes d'interface utilisateur graphique où les classes anonymes peuvent être remplacées par des expressions lambda. De même, il peut également être utilisé comme ceci lors de l'implémentation de l'interface Runnable :
Copiez le code comme suit :
//Utiliser une classe interne anonyme
btn.setOnAction(new EventHandler<ActionEvent>() {
@Outrepasser
handle public void (événement ActionEvent) {
System.out.println("Bonjour tout le monde !");
}
});
// Ou utilisez l'expression lambda
btn.setOnAction(event -> System.out.println("Hello World!"));
Voici un exemple d'utilisation de lambdas pour implémenter l'interface Runnable :
Copiez le code comme suit :
// 1.1 Utiliser des classes internes anonymes
nouveau fil (nouveau Runnable() {
@Outrepasser
public void run() {
System.out.println("Bonjour tout le monde !");
}
}).commencer();
// 1.2 Utiliser l'expression lambda
new Thread(() -> System.out.println("Bonjour tout le monde !")).start();
// 2.1 Utiliser des classes internes anonymes
Course exécutable1 = nouveau Runnable() {
@Outrepasser
public void run() {
System.out.println("Bonjour tout le monde !");
}
} ;
// 2.2 Utiliser l'expression lambda
Runnable race2 = () -> System.out.println("Bonjour tout le monde !");
// Appelez directement la méthode run (aucun nouveau thread n'est ouvert !)
race1.run();
race2.run();
L'expression lambda de Runnable utilise le format de bloc pour convertir cinq lignes de code en une seule instruction. Ensuite, dans la section suivante, nous utiliserons des lambdas pour trier la collection.
Tri des collections à l'aide de Lambdas
En Java, la classe Comparator est utilisée pour trier les collections. Dans l'exemple ci-dessous, nous nous baserons sur le nom, le prénom, la longueur du nom et la dernière lettre du joueur. Comme dans l'exemple précédent, nous utilisons d'abord une classe interne anonyme pour trier, puis utilisons des expressions lambda pour rationaliser notre code.
Dans le premier exemple, nous trierons la liste par nom. En utilisant l'ancienne méthode, le code ressemblerait à ceci :
Copiez le code comme suit :
String[] joueurs = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"} ;
// 1.1 Utiliser des classes internes anonymes pour trier les joueurs selon leur nom
Arrays.sort(players, new Comparator<String>() {
@Outrepasser
public int comparer (Chaîne s1, Chaîne s2) {
return (s1.compareTo(s2));
}
});
En utilisant lambdas, la même fonctionnalité peut être obtenue avec le code suivant :
Copiez le code comme suit :
// 1.2 Utiliser l'expression lambda pour trier les joueurs
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(joueurs, sortByName);
// 1.3 peut aussi prendre la forme suivante :
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
Les autres classements sont les suivants. Comme dans l'exemple ci-dessus, le code implémente Comparator via des classes internes anonymes et certaines expressions lambda :
Copiez le code comme suit :
// 1.1 Utiliser des classes internes anonymes pour trier les joueurs en fonction du nom de famille
Arrays.sort(players, new Comparator<String>() {
@Outrepasser
public int comparer (Chaîne s1, Chaîne s2) {
return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
}
});
// 1.2 Utiliser l'expression lambda pour trier par nom de famille
Comparateur<String> sortBySurname = (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(joueurs, sortBySurname);
// 1.3 Ou comme ça, je me demande si l'auteur original s'est trompé, il y a tellement de parenthèses...
Arrays.sort (joueurs, (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )
);
// 2.1 Utiliser des classes internes anonymes pour trier les joueurs en fonction de la longueur du nom
Arrays.sort(players, new Comparator<String>() {
@Outrepasser
public int comparer (Chaîne s1, Chaîne s2) {
return (s1.length() - s2.length());
}
});
// 2.2 Utiliser l'expression lambda pour trier en fonction de la longueur du nom
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(joueurs, sortByNameLenght);
// 2.3 ou ceci
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
// 3.1 Utiliser une classe interne anonyme pour trier les joueurs selon la dernière lettre
Arrays.sort(players, new Comparator<String>() {
@Outrepasser
public int comparer (Chaîne s1, Chaîne s2) {
return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
}
});
// 3.2 Utiliser l'expression lambda pour trier selon la dernière lettre
Comparateur<String> sortByLastLetter =
(Chaîne s1, Chaîne s2) ->
(s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(joueurs, sortByLastLetter);
// 3.3 ou ceci
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
Voilà, simple et intuitif. Dans la section suivante, nous explorerons davantage les capacités des lambdas et les utiliserons avec des flux.
Utilisation de Lambdas et de flux
Stream est un wrapper pour les collections et est généralement utilisé avec lambda. L'utilisation de lambdas peut prendre en charge de nombreuses opérations, telles que la carte, le filtre, la limite, le tri, le nombre, le min, le max, la somme, la collecte, etc. De même, les Streams utilisent des opérations paresseuses, ils ne lisent pas réellement toutes les données et la syntaxe de la chaîne se termine lorsqu'ils rencontrent des méthodes telles que getFirst(). Dans les exemples suivants, nous explorerons ce que les lambdas et les flux peuvent faire. Nous avons créé une classe Person et utilisé cette classe pour ajouter des données à la liste, qui seront utilisées pour d'autres opérations de streaming. Person n'est qu'une simple classe POJO :
Copiez le code comme suit :
classe publique Personne {
chaîne privée prénom, nom, travail, sexe ;
salaire international privé, âge;
Personne publique (String firstName, String lastName, String job,
Chaîne genre, âge int, salaire int) {
this.firstName = prénom;
this.lastName = lastName;
this.gender = sexe ;
this.age = âge;
this.job = travail ;
this.salary = salaire ;
}
// Getter et Setter
// .
}
Ensuite, nous allons créer deux listes, toutes deux utilisées pour stocker des objets Person :
Copiez le code comme suit :
List<Personne> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Elsdon", "Jaycob", "Programmeur Java", "male", 43, 2000));
add(new Person("Tamsen", "Bretagne", "Programmeur Java", "femelle", 23, 1500));
add(new Person("Floyd", "Donny", "Programmeur Java", "male", 33, 1800));
add(new Person("Sindy", "Jonie", "Programmeur Java", "femelle", 32, 1600));
add(new Person("Vere", "Hervey", "Programmeur Java", "male", 22, 1200));
add(new Person("Maude", "Jaimie", "Programmeur Java", "female", 27, 1900));
add(new Person("Shawn", "Randall", "Programmeur Java", "male", 30, 2300));
add(new Person("Jayden", "Corrina", "Programmeur Java", "femelle", 35, 1700));
add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
add(new Person("Addison", "Pam", "Programmeur Java", "femelle", 34, 1300));
}
} ;
List<Personne> phpProgrammers = new ArrayList<Person>() {
{
add(new Person("Jarrod", "Pace", "programmeur PHP", "male", 34, 1550));
add(new Person("Clarette", "Cicely", "Programmeur PHP", "femelle", 23, 1200));
add(new Person("Victor", "Channing", "Programmeur PHP", "male", 32, 1600));
add(new Person("Tori", "Sheryl", "programmeur PHP", "female", 21, 1000));
add(new Person("Osborne", "Shad", "programmeur PHP", "male", 32, 1100));
add(new Person("Rosalind", "Layla", "Programmeur PHP", "femelle", 25, 1300));
add(new Person("Fraser", "Hewie", "programmeur PHP", "male", 36, 1100));
add(new Person("Quinn", "Tamara", "programmeur PHP", "female", 21, 1000));
add(new Person("Alvin", "Lance", "Programmeur PHP", "male", 38, 1600));
add(new Person("Evonne", "Shari", "programmeur PHP", "female", 40, 1800));
}
} ;
Nous utilisons maintenant la méthode forEach pour parcourir et afficher la liste ci-dessus :
Copiez le code comme suit :
System.out.println("Noms de tous les programmeurs :");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Nous utilisons également la méthode forEach pour augmenter le salaire du programmeur de 5 % :
Copiez le code comme suit :
System.out.println("Donnez aux programmeurs une augmentation de salaire de 5 % :");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);
Une autre méthode utile est filter(), qui nous permet d'afficher les programmeurs PHP avec un salaire mensuel supérieur à 1 400 $ :
Copiez le code comme suit :
System.out.println("Voici des programmeurs PHP avec un salaire mensuel de plus de 1 400 $ :")
phpProgrammers.stream()
.filter((p) -> (p.getSalary() > 1400))
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
On peut également définir des filtres puis les réutiliser pour effectuer d'autres opérations :
Copiez le code comme suit :
// définit les filtres
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaireFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> GenderFilter = (p) -> ("female".equals(p.getGender()));
System.out.println("Voici des programmeuses PHP âgées de plus de 24 ans et ayant un salaire mensuel de plus de 1 400 $ :");
phpProgrammers.stream()
.filter(ageFilter)
.filter(salaryFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
//Réutiliser les filtres
System.out.println("Programmeuses Java de plus de 24 ans :");
javaProgrammers.stream()
.filter(ageFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Utilisez la méthode limit pour limiter le nombre de jeux de résultats :
Copiez le code comme suit :
System.out.println("Les 3 premiers programmeurs Java :");
javaProgrammers.stream()
.limite(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("Les 3 meilleures programmeuses Java :");
javaProgrammers.stream()
.filter(genderFilter)
.limite(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Qu'en est-il du tri ? Pouvons-nous le gérer dans le flux ? Dans l'exemple suivant, nous allons trier les programmeurs Java par nom et salaire, les mettre dans une liste, puis afficher la liste :
Copiez le code comme suit :
System.out.println("Trier par nom et afficher les 5 meilleurs programmeurs Java :");
Liste<Personne> triéeJavaProgrammers = javaProgrammers
.flux()
.sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
.limite(5)
.collect(toList());
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
System.out.println("Trier les programmeurs Java selon leur salaire :");
triésJavaProgrammers = javaProgrammers
.flux()
.sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
.collect( toList() );
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
Si l’on ne s’intéresse qu’aux salaires les plus bas et les plus élevés, ce qui est plus rapide que de sélectionner le premier/dernier après tri, ce sont les méthodes min et max :
Copiez le code comme suit :
System.out.println("Le programmeur Java le moins bien payé :");
Personne pers = javaProgrammeurs
.flux()
.min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
.obtenir()
System.out.printf("Nom : %s %s ; Salaire : $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
System.out.println("Programmeur Java avec le salaire le plus élevé :");
Personne personne = javaProgrammeurs
.flux()
.max((p, p2) -> (p.getSalary() - p2.getSalary()))
.obtenir()
System.out.printf("Nom : %s %s ; Salaire : $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
Dans l’exemple ci-dessus, nous avons vu comment fonctionne la méthode collect. En conjonction avec la méthode map, nous pouvons utiliser la méthode collect pour mettre notre ensemble de résultats dans un String, un Set ou un TreeSet :
Copiez le code comme suit :
System.out.println("Concaténer le prénom des programmeurs PHP en une chaîne :");
Chaîne phpDevelopers = phpProgrammers
.flux()
.map(Personne::getFirstName)
.collect(joining(" ; ")); // Peut être utilisé comme jeton dans d'autres opérations
System.out.println("Enregistrez le prénom des programmeurs Java dans Set:");
Set<String> javaDevFirstName = javaProgrammers
.flux()
.map(Personne::getFirstName)
.collect(toSet());
System.out.println("Enregistrez le prénom des programmeurs Java dans TreeSet :");
TreeSet<String> javaDevLastName = javaProgrammers
.flux()
.map(Personne::getLastName)
.collect(toCollection(TreeSet::new));
Les flux peuvent également être parallèles. Les exemples sont les suivants :
Copiez le code comme suit :
System.out.println("Calculez tout l'argent payé aux programmeurs Java :");
int totalSalary = javaProgrammers
.parallelStream()
.mapToInt(p -> p.getSalary())
.somme();
Nous pouvons utiliser la méthode summaryStatistics pour obtenir diverses données récapitulatives des éléments du flux. Ensuite, nous pouvons accéder à ces méthodes telles que getMax, getMin, getSum ou getAverage :
Copiez le code comme suit :
//Calculer le nombre, le min, le max, la somme et la moyenne des nombres
List<Integer> nombres = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Statistiques IntSummaryStatistics = nombres
.flux()
.mapToInt((x) -> x)
.summaryStatistics();
System.out.println("Le plus grand nombre de la liste : " + stats.getMax());
System.out.println("Le plus petit nombre de la liste : " + stats.getMin());
System.out.println("Somme de tous les nombres : " + stats.getSum());
System.out.println("Moyenne de tous les nombres : " + stats.getAverage());
OK, ça y est, j'espère que ça vous plaira !
Résumer
Dans cet article, nous avons appris différentes manières d'utiliser les expressions lambda, depuis des exemples de base jusqu'à des exemples plus complexes utilisant des lambdas et des flux. De plus, nous avons également appris à utiliser les expressions lambda et la classe Comparator pour trier les collections Java.
Note du traducteur : bien qu'elle semble très avancée, l'essence d'une expression Lambda n'est qu'un « sucre de syntaxe » qui est déduit par le compilateur et vous aide à la convertir et à l'envelopper dans du code normal, afin que vous puissiez utiliser moins de code pour obtenir la même fonction. . Je recommande de ne pas l'utiliser sans discernement, car il ressemble au code écrit par certains hackers très avancés. Il est concis, difficile à comprendre et difficile à déboguer, et le personnel de maintenance voudra vous gronder.