Mon point de vue sur les expressions de lambda en java est assez emmêlé:
Celui que je pense de cette façon: les expressions de lambda réduisent l'expérience de lecture des programmes Java. Les programmes Java n'ont jamais été exceptionnels dans l'expressivité. Au contraire, l'un des facteurs qui rend Java populaire est sa sécurité et son conservatisme - même les débutants peuvent écrire un code robuste et facile à maintenir tant qu'ils y prêtent attention. Les expressions de lambda ont des exigences relativement plus élevées pour les développeurs, ils augmentent donc également certaines difficultés de maintenance.
Une autre chose que je pense est: en tant que code de code, il est nécessaire d'apprendre et d'accepter de nouvelles fonctionnalités de la langue. Si vous abandonnez ses forces expressives simplement à cause de sa mauvaise expérience de lecture, certaines personnes ont du mal à comprendre même les expressions trinoculaires. La langue se développe également, et ceux qui ne peuvent pas suivre seront laissés volontairement.
Je ne veux pas être laissé pour compte. Cependant, si je devais faire un choix, ma décision était encore relativement conservatrice: il n'est pas nécessaire d'utiliser Lambda dans la langue Java - cela rend de nombreuses personnes dans le cercle Java actuel inhabituel et entraînera une augmentation des coûts de main-d'œuvre. Si vous l'aimez beaucoup, vous pouvez envisager d'utiliser Scala.
Quoi qu'il en soit, j'ai quand même commencé à essayer de maîtriser Lambda, après tout, une partie du code maintenu au travail utilise Lambda (croyez-moi, je le supprime progressivement). Les tutoriels à apprendre sont des tutoriels connexes sur le site officiel d'Oracle Java.
―Oie
Supposons qu'une application de réseau social soit actuellement en cours de création. Une caractéristique est que les administrateurs peuvent effectuer certaines actions sur les membres qui répondent aux critères spécifiés, tels que l'envoi de messages. Le tableau suivant décrit ce cas d'utilisation en détail:
| Champ | décrire |
| nom | Actions à effectuer |
| Participants clés | administrateur |
| Condition préalable | Connexion administratrice au système |
| Post-condition | Effectuer uniquement des actions pour les membres qui répondent aux critères spécifiés |
| Scénario de réussite principal | 1. L'administrateur établit des normes de filtrage pour les membres cibles pour effectuer l'opération; 2. L'administrateur sélectionne l'action à effectuer; 3. L'administrateur clique sur le bouton Soumettre; 4. Le système trouve des membres qui répondent aux critères spécifiés; 5. Le système effectue des opérations présélectionnées sur les membres qui répondent aux critères spécifiés. |
| Étendu | Avant de sélectionner l'opération d'exécution ou avant de cliquer sur le bouton Soumettre, l'administrateur peut choisir de prévisualiser les informations des membres qui répondent aux critères de filtrage. |
| Fréquence d'occurrence | Cela arrive plusieurs fois par jour. |
Utilisez la classe de personne suivante pour représenter les informations des membres dans les réseaux sociaux:
classe publique Personne {public Enum Sex {masculin, femelle} nom de chaîne; Anniversaire localDate; Genre sexuel; String EmailAddress; public int getage () {// ...} public void PRINTPERSON () {// ...}}Supposons que tous les membres sont enregistrés dans une instance de liste <ponse>.
Dans cette section, nous commençons par une méthode très simple, puis essayons de la mettre en œuvre à l'aide de classes locales et de classes anonymes, et à la fin, nous connaîtrons progressivement la puissance et l'efficacité des expressions de Lambda. Le code complet peut être trouvé ici.
Solution 1: Créez des méthodes pour trouver des membres qui répondent aux critères spécifiés un par un
Il s'agit de la solution la plus simple et la plus rugueuse pour implémenter les cas susmentionnés: il s'agit de créer plusieurs méthodes et chaque méthode vérifie un critère (comme l'âge ou le sexe). Le code suivant vérifie que l'âge est plus âgé qu'une valeur spécifiée:
Public Static Void PrintPersonsolderthan (List <Songe> Roster, int Age) {for (Person P: Roster) {if (p.getAgE ()> = Age) {p.PrintSerson (); }}}Il s'agit d'une solution très fragile, et il est très probable que l'application ne s'exécute pas en raison d'une petite mise à jour. Si nous ajoutons de nouvelles variables de membre à la classe de personne ou modifions l'algorithme pour mesurer l'âge dans la norme, nous devons réécrire beaucoup de code pour nous adapter à ce changement. De plus, les restrictions ici sont trop rigides. Par exemple, que devons-nous faire si nous voulons imprimer des membres plus jeunes qu'une valeur spécifiée? Ajoutez une autre nouvelle méthode PrintPersonsyoungerThan? C'est évidemment une méthode stupide.
Solution 2: Créez une méthode plus générale
La méthode suivante est plus adaptable que PrintPersonsolderthan; Cette méthode imprime les informations des membres dans le groupe d'âge spécifié:
Public Static Void PrintPersonsonsWithInagerange (List <Songe> Roster, int low, int high) {for (personne P: Roster) {if (Low <= P.Getage () && p.getage () <high) {p.PrintSerson (); }}}Maintenant, il y a une nouvelle idée: que devons-nous faire si nous voulons imprimer des informations sur les membres du sexe spécifié, ou qui répond au sexe spécifié et se situe dans le groupe d'âge spécifié? Et si nous ajustons la classe de personne et ajoutons des propriétés telles que l'amitié et l'emplacement géographique. Bien que des méthodes d'écriture comme celle-ci soient plus universelles que les imprimés de gravure-poule, l'écriture d'une méthode pour chaque requête possible peut également conduire à une fragilité dans le code. Il est préférable de mettre le code de vérification standard dans une nouvelle classe.
Solution 3: implémenter une inspection standard dans une classe locale
La méthode suivante imprime les informations des membres qui répondent aux critères de recherche:
Public static void PrintPersons (liste <ony> Roster, CheckPerson Tester) {for (Person P: Roster) {if (Tester.Test (P)) {p.PrintPerson (); }}}Un testeur d'objet CheckPerso est utilisé dans le programme pour vérifier chaque instance dans la liste des paramètres de liste. Si Tester.Test () renvoie true, la méthode PrintPerson () sera exécutée. Afin de définir les critères de recherche, l'interface CheckPerson doit être implémentée.
La classe suivante implémente CheckPerson et fournit une implémentation spécifique de la méthode de test. La méthode d'essai dans cette classe filtre les informations sur l'adhésion qui répond aux exigences en matière de service militaire aux États-Unis: c'est-à-dire le sexe et l'âge masculin entre 18 et 25 ans.
Class CheckPersonElibleForsElectiveService implémente CheckPerson {public boolean test (Person P) {return p.gender == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25; }}Pour utiliser cette classe, vous devez créer une instance et déclencher la méthode des gravures:
Printersons (liste, nouveau CheckPersonElibleForsElectiveService ());
Le code semble désormais moins fragile - nous n'avons pas besoin de réécrire le code en raison des modifications de la structure de la classe de personne. Cependant, il y a encore du code supplémentaire ici: une interface nouvellement définie qui définit une classe interne pour chaque norme de recherche dans l'application.
Étant donné que CheckPersonElibleForsElectiveService implémente une interface, une classe anonyme peut être utilisée sans définir une classe intérieure pour chaque norme.
Solution 4: Utilisez des classes anonymes pour implémenter une inspection standard
Un paramètre dans la méthode des gravures appelée ci-dessous est la classe anonyme. La fonction de cette classe anonyme est la même que celle de la classe CheckPersonligibleForselectiveService dans le schéma 3: Ce sont tous des membres filtrés avec un sexe masculin et des âges entre 18 et 25 ans.
PRINTPERSONS (RISSER, NOUVEAU CHECKSERSON () {Public Boolean Test (Person P) {return P.GetGender () == Person.SEX.Male && P.Getage ()> = 18 && P.Getage () <= 25;}});Ce schéma réduit la quantité de codage, car il n'est plus nécessaire de créer de nouvelles classes pour que chaque schéma de recherche soit exécuté. Cependant, il est encore un peu inconfortable de le faire: bien que l'interface CheckPerson n'a qu'une seule méthode, la classe anonyme implémentée est toujours un peu verbeux et volumineuse. Pour le moment, vous pouvez utiliser l'expression de Lambda pour remplacer les classes anonymes. Ce qui suit expliquera comment utiliser l'expression de Lambda pour remplacer les classes anonymes.
Solution 5: Utilisez des expressions Lambda pour implémenter une vérification standard
L'interface CheckPerson est une interface fonctionnelle. La soi-disant interface fonctionnelle fait référence à toute interface qui ne contient qu'une seule méthode abstraite. (Une interface fonctionnelle peut également avoir plusieurs méthodes par défaut ou méthodes statiques). Puisqu'il n'y a qu'une seule méthode abstraite dans l'interface fonctionnelle, le nom de la méthode de la méthode peut être omis lors de l'implémentation de la méthode de cette interface fonctionnelle. Pour implémenter cette idée, vous pouvez remplacer les expressions de classe anonymes par des expressions Lambda. Dans la méthode des gravures réécrites ci-dessous, le code pertinent est mis en surbrillance:
PrintPersons (liste, (personne p) -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25);
Ici, vous pouvez également utiliser une interface de fonction standard pour remplacer l'interface CheckPerson, ce qui simplifie davantage le code.
Solution 6: Utilisez des interfaces fonctionnelles standard dans les expressions de lambda
Jetons un coup d'œil à l'interface CheckPerson:
Interface CheckPerson {Boolean Test (Person P); }Il s'agit d'une interface très simple. Parce qu'il n'y a qu'une seule méthode abstraite, c'est aussi une interface fonctionnelle. Cette méthode abstraite n'accepte qu'un seul paramètre et renvoie une valeur booléenne. Cette interface abstraite est si simple que nous examinerons s'il est nécessaire de définir une telle interface dans l'application. Pour le moment, vous pouvez envisager d'utiliser des interfaces fonctionnelles standard définies par JDK, et vous pouvez trouver ces interfaces dans le package java.util.function.
Dans cet exemple, nous pouvons utiliser l'interface prédicat <t> pour remplacer CheckPerson. Il existe une méthode de test booléen (t t) dans cette interface:
prédicat d'interface <T> {test booléen (t t); }L'interface prédicat <t> est une interface générique. Une classe générique (ou une interface générique) spécifie un ou plusieurs paramètres de type utilisant une paire de supports d'angle (<>). Il n'y a qu'un seul paramètre de type dans cette interface. Lorsque vous déclarez ou instanciez une classe générique à l'aide d'une classe en béton, vous obtenez une classe paramétrée. Par exemple, le prédicat de classe paramétré <onge> est comme ceci:
Prédicat d'interface <on personne> {test booléen (personne t); }Dans cette classe paramétrée, il existe une méthode cohérente avec les paramètres et les valeurs de retour de la méthode CheckPerson.Boolean Test (Person P). Par conséquent, vous pouvez utiliser l'interface prédicat <t> pour remplacer l'interface CheckPerson comme démontré dans la méthode suivante:
Public Static void PrintPersonsonsWithPredicate (liste <onge> Roster, prédicat <onon> testeur) {for (personne p: roster) {if (tester.test (p)) {p.PrintPerson (); }}}Utilisez ensuite le code suivant pour filtrer les membres du service militaire comme dans le plan 3:
PRINTPERSONSONWITHPREdicate (Roster, P -> P.GetGender () == Person.SEX.MALE && P.Getage ()> = 18 && p.getage () <= 25);
Avez-vous remarqué que lors de l'utilisation du prédicat <ponse> comme type de paramètre, aucun type de paramètre explicite n'est spécifié. Ce n'est pas le seul endroit où les expressions lambda sont appliquées. Le schéma suivant introduira davantage d'utilisation des expressions de lambda.
Solution 7: Utilisez des expressions lambda tout au long de l'application
Jetons un coup d'œil à la méthode PrintPersonsonsonsonwithPredicate et examinons si vous pouvez utiliser les expressions Lambda ici:
Public Static void PrintPersonsonsWithPredicate (liste <onge> Roster, prédicat <onon> testeur) {for (personne p: roster) {if (tester.test (p)) {p.PrintPerson (); }}}Dans cette méthode, chaque instance de personne dans la liste est vérifiée à l'aide du testeur d'instance de prédicat. Si l'instance de personne est conforme aux critères de vérification définis dans le testeur, la méthode de l'imprimé de l'instance de personne sera déclenchée.
En plus de déclencher la méthode de Printerson, les instances de personne qui respectent la norme du testeur peuvent également exécuter d'autres méthodes. Vous pouvez envisager d'utiliser une expression lambda pour spécifier la méthode à exécuter (je pense que cette fonctionnalité est bonne, ce qui résout le problème que les méthodes en Java ne peuvent pas être transmises en tant qu'objets). Maintenant, vous avez besoin d'une expression de lambda similaire à la méthode de Printperson - une expression de lambda qui ne nécessite qu'un seul paramètre et renvoie vide. N'oubliez pas une chose: pour utiliser les expressions Lambda, vous devez d'abord implémenter une interface fonctionnelle. Dans cet exemple, une interface fonctionnelle est nécessaire, qui ne contient qu'une seule méthode abstraite. Cette méthode abstraite a un paramètre de type de type et revient au vide. Vous pouvez jeter un œil au consommateur d'interface fonctionnelle standard <T> fournis par JDK, qui a une méthode abstraite vide accepter (t) ne répond qu'à cette exigence. Dans le code suivant, utilisez une instance de consommateur <T> pour appeler la méthode d'acceptation au lieu de p.printSerson ():
Public static void ProcessPersons (liste <on personne>, Prédicat <ponge> Tester, Consumer <Sonfing> Block) {for (Person P: Roster) {if (Tester.Test (P)) {Block.Accept (P); }}}En conséquence, vous pouvez utiliser le code suivant pour filtrer les membres de l'âge de service militaire:
ProcessPersons (Roster, P -> P.GetGender () == Person.SEX.MALE && P.Getage ()> = 18 && P.Getage () <= 25, P -> p.PrintSerson ());
Si nous voulons faire des choses qui n'impriment pas seulement les informations des membres, mais plus de choses, telles que la vérification de l'appartenance, l'obtention des coordonnées des membres, etc. À ce stade, nous avons besoin d'une interface fonctionnelle avec une méthode de valeur de retour. La fonction d'interface fonctionnelle standard de JDK <T, R> a une méthode comme celle R s'applique (t t). La méthode suivante obtient des données du mappeur de paramètres et effectue le comportement spécifié par le bloc de paramètres sur ces données:
public static void processPersonsonsWithFunction (list <onon> Roster, prédicat <onon> testeur, fonction <personne, string> mapper, consommateur <string> block) {for (personne p: roster) {if (tester.test (p)) {String data = mapper.apply (p); Block.accept (données); }}}Le code suivant obtient les informations par e-mail de tous les membres de l'âge du service militaire dans la liste et les imprime:
ProcessPersonsonsWithFunction (Roster, P -> P.GetGender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.getEmailAddress (), Email -> System.out.println (e-mail));
Solution 8: Utilisez les génériques plus fréquemment
Passons en revue la méthode ProcessPersonsonsons avec une fonction. Ce qui suit est une version générique de cette méthode. La nouvelle méthode nécessite plus de tolérance dans les types de paramètres:
public static <x, y> void ProcessElements (itéable <x> source, prédicat <x> testeur, fonction <x, y> mappeur, consommateur <y> block) {for (x p: source) {if (tester.test (p)) {y data = mapper.Apply (p); Block.accept (données); }}}Pour imprimer des informations sur les membres pour le service militaire au bon âge, vous pouvez appeler la méthode des éléments de processus comme ce qui suit:
ProcessElements (Roster, P -> P.GetGender () == Person.SEX.Male && P.Getage ()> = 18 && P.Getage () <= 25, P -> P.GetEmailAddress (), Email -> System.out.println (email));
Pendant le processus d'appel de méthode, le comportement suivant est effectué:
Obtenez des informations d'objet à partir d'une collection, dans cet exemple, obtenez des informations d'objet de personne à partir de la liste d'instructions de collection.
Filtrez des objets qui peuvent correspondre au testeur d'instance de prédicat. Dans cet exemple, l'objet de prédicat est une expression de lambda qui spécifie les conditions de filtrage du service militaire au bon âge.
L'objet filtré est remis à un mappeur d'objet de fonction pour le traitement, et le mappeur correspondra à une valeur à cet objet. Dans cet exemple, le mappeur d'objet de fonction est une expression lambda qui renvoie l'adresse e-mail de chaque membre.
Spécifie un comportement par le bloc d'objets grand public pour la valeur correspondante par le mappeur. Dans cet exemple, l'objet Consumer est une expression de lambda, qui est la fonction d'impression d'une chaîne, qui est l'adresse e-mail membre renvoyée par le mappeur d'instance de fonction.
Solution 9: Utilisez un opération d'agrégation en utilisant l'expression de lambda comme paramètre
Le code suivant utilise un opération d'agrégation pour imprimer les adresses e-mail des membres de l'âge militaire dans la collection de liste:
Roster.Stream () .Filter (P -> P.GetGender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25) .map (p -> p.getEmailAddress ()) .ForEach (e-mail -> System.out.println (e-mail));
Analyser le processus d'exécution du code ci-dessus et organiser le tableau suivant:
Comportement | Opération d'agrégation |
Obtenir l'objet | Stream <e> Stream () |
Filtre des objets qui correspondent aux critères spécifiés de l'instance de prédicat | Stream <T> Filtre (prédicat <? Super T> Prédire) |
Obtenez la valeur de correspondance de l'objet via une instance de fonction | <r> Stream <r> Carte (fonction <? Super T,? Étend R> Mapper) |
Exécuter un comportement spécifié par l'instance de consommation | void foreach (Consumer <? Super T> Action) |
Les opérations de filtre, de carte et de foreach dans le tableau sont toutes des opérations agrégées. Les éléments traités par l'opération d'agrégation proviennent du flux, et non directement de la collection (c'est-à-dire, car la première méthode appelée dans cet exemple de programme est Stream ()). Un flux est une séquence de données. Contrairement aux collections, Stream ne stocke pas de données avec une structure spécifique. Au lieu de cela, Stream obtient des données à partir d'une source spécifique, comme à partir d'une collection, via un pipeline. Le pipeline est une séquence de fonctionnement du flux, dans cet exemple de Filter-MAP-forach. De plus, les opérations d'agrégation utilisent généralement les expressions Lambda comme paramètres, ce qui nous donne également beaucoup d'espace personnalisé.