JavaRush /Blog Java /Random-FR /Traduction du livre. Programmation fonctionnelle en Java....
timurnav
Niveau 21

Traduction du livre. Programmation fonctionnelle en Java. Chapitre 1

Publié dans le groupe Random-FR
Je serai heureux de vous aider à trouver les erreurs et à améliorer la qualité de la traduction. Je traduis pour améliorer mes compétences en anglais, et si vous lisez et recherchez des erreurs de traduction, vous vous améliorerez encore mieux que moi. L'auteur du livre écrit que le livre nécessite beaucoup d'expérience avec Java ; pour être honnête, je ne suis pas moi-même particulièrement expérimenté, mais j'ai compris le contenu du livre. Le livre traite d'une théorie difficile à expliquer avec les doigts. S'il y a des articles décents sur le wiki, je fournirai des liens vers eux, mais pour une meilleure compréhension, je vous recommande de le rechercher vous-même sur Google. bonne chance à tous. :) Pour ceux qui souhaitent corriger ma traduction, ainsi que pour ceux qui la trouvent trop mauvaise pour lire en russe, vous pouvez télécharger le livre original ici . Contenu Chapitre 1 Bonjour, Expressions Lambda - en cours de lecture Chapitre 2 Utilisation des collections - en développement Chapitre 3 Chaînes, comparateurs et filtres - en développement Chapitre 4 Développement avec des expressions Lambda - en développement Chapitre 5 Travailler avec des ressources - en développement Chapitre 6 Être paresseux - en développement Chapitre 7 Optimiser les ressources - en développement Chapitre 8 Disposition avec des expressions lambda - en développement Chapitre 9 Rassembler le tout - en développement

Chapitre 1 Bonjour, expressions Lambda !

Notre code Java est prêt pour des transformations remarquables. Les tâches quotidiennes que nous effectuons deviennent plus simples, plus faciles et plus expressives. La nouvelle façon de programmer Java est utilisée depuis des décennies dans d’autres langages. Grâce à ces modifications apportées à Java, nous pouvons écrire du code concis, élégant et expressif avec moins d'erreurs. Nous pouvons l'utiliser pour appliquer facilement des normes et implémenter des modèles de conception communs avec moins de lignes de code. Dans ce livre, nous explorons le style fonctionnel de la programmation à l'aide d'exemples simples de problèmes que nous résolvons quotidiennement. Avant de plonger dans ce style élégant et cette nouvelle façon de développer des logiciels, voyons pourquoi c'est mieux.
Changez votre façon de penser
Le style impératif est ce que Java nous a offert depuis la création du langage. Ce style suggère que nous décrivions à Java chaque étape de ce que nous voulons que le langage fasse, puis que nous nous assurions simplement que ces étapes sont fidèlement suivies. Cela a très bien fonctionné, mais le niveau reste faible. Le code s'est avéré trop verbeux et nous avons souvent voulu un langage un peu plus intelligent. Nous pourrions alors le dire de manière déclarative – ce que nous voulons, et ne pas nous demander comment le faire. Grâce aux développeurs, Java peut désormais nous aider à y parvenir. Examinons quelques exemples pour comprendre les avantages et les différences entre ces approches.
La manière habituelle
Commençons par des bases familières pour voir les deux paradigmes en action. Cela utilise une méthode impérative pour rechercher Chicago dans la collection des villes - les listes de ce livre n'affichent que des extraits de code. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); La version impérative du code est bruitée (qu'est-ce que ce mot a à voir là-dedans ?) et de bas niveau, il y a plusieurs parties mutables. Nous créons d’abord ce drapeau booléen puant appelé found , puis nous parcourons chaque élément de la collection. Si nous trouvons la ville que nous recherchons, nous mettons le drapeau sur vrai et rompons la boucle. Enfin nous imprimons le résultat de notre recherche sur la console.
Il y a une meilleure façon
En tant que programmeurs Java observateurs, un simple coup d'œil sur ce code peut le transformer en quelque chose de plus expressif et plus facile à lire, comme ceci : System.out.println("Found chicago?:" + cities.contains("Chicago")); Voici un exemple du style déclaratif - la méthode contain() nous aide à accéder directement à ce dont nous avons besoin.
Changements réels
Ces changements apporteront un certain nombre d'améliorations à notre code :
  • Pas de problème avec les variables mutables
  • Les itérations de boucle sont cachées sous le capot
  • Moins d'encombrement de code
  • Une plus grande clarté du code, concentre l'attention
  • Moins d'impédance ; le code suit de près l'intention commerciale
  • Moins de risque d'erreur
  • Plus facile à comprendre et à prendre en charge
Au-delà des cas simples
Il s'agit d'un exemple simple de fonction déclarative qui vérifie la présence d'un élément dans une collection ; elle est utilisée depuis longtemps en Java. Imaginez maintenant ne pas avoir à écrire de code impératif pour des opérations plus avancées telles que l'analyse de fichiers, l'utilisation de bases de données, la création de requêtes de services Web, la création de multithreading, etc. Java permet désormais d'écrire du code concis et élégant qui rend plus difficile les erreurs, non seulement dans les opérations simples, mais dans l'ensemble de notre application.
L'ancienne manière
Regardons un autre exemple. Nous créons une collection avec des prix et essaierons plusieurs façons de calculer la somme de tous les prix réduits. Supposons qu'on nous demande de résumer tous les prix dont la valeur dépasse 20 $, avec une remise de 10 %. Commençons par procéder de la manière Java habituelle. Ce code devrait nous être très familier : nous créons d’abord une variable mutable totalOfDiscountedPrices dans laquelle nous stockerons la valeur résultante. Nous parcourons ensuite la collection de prix, sélectionnons les prix supérieurs à 20 $, obtenons le prix réduit et ajoutons cette valeur à totalOfDiscountedPrices . A la fin nous affichons la somme de tous les prix en tenant compte de la remise. Voici ce qui est affiché sur la console final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Total des prix réduits : 67,5
Cela fonctionne, mais le code semble compliqué. Mais ce n'est pas de notre faute, nous avons utilisé ce qui était disponible. Le code est d'un niveau assez bas - il souffre d'une obsession pour les primitives (google, trucs intéressants) et il va à l'encontre du principe de responsabilité unique . Ceux d'entre nous qui travaillent à la maison devraient garder ce code hors de portée des enfants aspirant à devenir programmeurs. Cela pourrait alarmer leur esprit fragile. Préparez-vous à la question "Est-ce que c'est ce que vous devez faire pour survivre ?"
Il y a une meilleure façon, une autre
Maintenant, nous pouvons faire mieux, beaucoup mieux. Notre code peut ressembler à une exigence de spécification. Cela nous aidera à réduire l’écart entre les besoins de l’entreprise et le code qui les met en œuvre, réduisant ainsi davantage le risque de mauvaise interprétation des exigences. Au lieu de créer une variable puis de la modifier à plusieurs reprises, travaillons à un niveau d'abstraction plus élevé, comme dans la liste suivante. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Lisons-le à voix haute : le filtre de prix est supérieur à 20, mappez (créez des paires "clé" "valeur") à l'aide de la clé "prix", prix incluant la remise, puis ajoutez-les
- le commentaire du traducteur désigne les mots qui apparaissent dans votre tête lors de la lecture du code .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
Le code est exécuté ensemble dans la même séquence logique que celle que nous avons lue. Le code a été raccourci, mais nous avons utilisé tout un tas de nouveautés de Java 8. Tout d'abord, nous avons appelé la méthode stream() sur la liste de prix . Cela ouvre la porte à un itérateur personnalisé doté d’un riche ensemble de fonctionnalités pratiques dont nous parlerons plus tard. Au lieu de parcourir directement toutes les valeurs de la liste de prix , nous utilisons plusieurs méthodes spéciales telles que filter() et map() . Contrairement aux méthodes que nous avons utilisées dans Java et le JDK, ces méthodes prennent une fonction anonyme – une expression lambda – comme paramètre entre parenthèses. Nous l'étudierons plus en détail plus tard. En appelant la méthode réduire() , on calcule la somme des valeurs (prix réduit) obtenues dans la méthode map() . La boucle est masquée de la même manière que lors de l'utilisation de la méthode contain() . Les méthodes filter() et map() sont cependant encore plus complexes. Pour chaque prix de la liste de prix , ils appellent la fonction lambda transmise et l'enregistrent dans une nouvelle collection. La méthode réduire() est appelée sur cette collection pour produire le résultat final. Voici ce qui est affiché sur la console
Total des prix réduits : 67,5
Changements
Voici les changements par rapport à la méthode habituelle :
  • Le code est agréable à regarder et peu encombré.
  • Aucune opération de bas niveau
  • Plus facile d’améliorer ou de changer la logique
  • L'itération est contrôlée par une bibliothèque de méthodes
  • Évaluation de boucle efficace et paresseuse
  • Plus facile à paralléliser selon les besoins
Nous verrons plus tard comment Java apporte ces améliorations.
Lambda à la rescousse :)
Lambda est la clé fonctionnelle pour nous libérer des tracas de la programmation impérative. En changeant la façon dont nous programmons, avec les dernières fonctionnalités de Java, nous pouvons écrire du code non seulement élégant et concis, mais également moins sujet aux erreurs, plus efficace et plus facile à optimiser, à améliorer et à créer en multithread.
Gagnez gros grâce à la programmation fonctionnelle
Le style de programmation fonctionnelle a un rapport signal/bruit plus élevé ; Nous écrivons moins de lignes de code, mais chaque ligne ou expression exécute plus de fonctionnalités. Nous avons peu gagné de la version fonctionnelle du code par rapport à la version impérative :
  • Nous avons évité les modifications ou réaffectations indésirables de variables, sources d'erreurs et rendant difficile le traitement simultané du code de différents threads. Dans la version impérative, nous définissons différentes valeurs pour la variable totalOfDiscountedPrices tout au long de la boucle . Dans la version fonctionnelle, il n'y a pas de changement explicite de variable dans le code. Moins de changements entraînent moins de bugs dans le code.
  • La version fonctionnelle du code est plus facile à paralléliser. Même si les calculs de la méthode map() étaient longs, on peut les exécuter en parallèle sans crainte de rien. Si nous accédons au code de style impératif à partir de différents threads, nous devrons nous soucier de modifier la variable totalOfDiscountedPrices en même temps . Dans la version fonctionnelle, nous accédons à la variable seulement après que toutes les modifications ont été apportées, cela nous évite de nous soucier de la sécurité des threads du code.
  • Le code est plus expressif. Au lieu d'exécuter le code en plusieurs étapes - créer et initialiser une variable avec une valeur factice, parcourir la liste de prix, ajouter des prix réduits à la variable, etc. - nous demandons simplement à la méthode map() de la liste de renvoyer une autre liste. de prix réduits et additionnez -les .
  • Le style fonctionnel est plus concis : moins de lignes de code sont nécessaires que la version impérative. Un code plus compact signifie moins à écrire, moins à lire et plus facile à maintenir.
  • La version fonctionnelle du code est intuitive et facile à comprendre, une fois que l’on connaît sa syntaxe. La méthode map() applique la fonction passée (qui calcule le prix réduit) à chaque élément de la collection et produit une collection avec le résultat, comme nous pouvons le voir dans l'image ci-dessous.

Image Figure 1 - la méthode map applique la fonction transmise à chaque élément de la collection
Avec la prise en charge des expressions lambda, nous pouvons exploiter pleinement la puissance du style fonctionnel de programmation en Java. Si nous maîtrisons ce style, nous pouvons créer un code plus expressif et plus concis avec moins de modifications et d’erreurs. Auparavant, l'un des principaux avantages de Java était sa prise en charge du paradigme orienté objet. Et le style fonctionnel ne contredit pas la POO. Une réelle excellence pour passer de la programmation impérative à la programmation déclarative. Avec Java 8, nous pouvons combiner assez efficacement la programmation fonctionnelle avec un style orienté objet. Nous pouvons continuer à appliquer le style OO aux objets, à leur portée, leur état et leurs relations. De plus, nous pouvons modéliser le comportement et l’état de changement, les processus métier et le traitement des données sous la forme d’une série d’ensembles de fonctions.
Pourquoi coder dans un style fonctionnel ?
Nous avons vu les avantages globaux du style de programmation fonctionnel, mais ce nouveau style vaut-il la peine d'être appris ? S’agira-t-il d’un changement mineur de langage ou cela changera-t-il nos vies ? Nous devons obtenir des réponses à ces questions avant de perdre notre temps et notre énergie. Écrire du code Java n’est pas si difficile ; la syntaxe du langage est simple. Nous sommes à l’aise avec les bibliothèques et API familières. Ce qui nous oblige vraiment à déployer des efforts pour écrire et maintenir du code, ce sont les applications d'entreprise typiques pour lesquelles nous utilisons Java pour le développement. Nous devons nous assurer que les autres programmeurs ferment les connexions à la base de données au bon moment, qu'ils ne la conservent pas ou n'effectuent pas de transactions plus longtemps que nécessaire, qu'ils interceptent complètement et au bon niveau les exceptions, qu'ils appliquent et libèrent les verrous correctement. ... cette feuille peut être continuée très longtemps. Chacun des arguments ci-dessus n’a à lui seul aucun poids, mais ensemble, lorsqu’ils sont combinés aux complexités inhérentes à la mise en œuvre, ils deviennent écrasants, longs et difficiles à mettre en œuvre. Et si nous pouvions encapsuler ces complexités dans de minuscules morceaux de code qui pourraient également bien les gérer ? Nous ne dépenserions alors plus d’énergie constamment à mettre en œuvre des normes. Cela donnerait un sérieux avantage, alors regardons comment un style fonctionnel peut aider.
Joe demande
Un code court* signifie-t-il simplement moins de lettres de code ?
* nous parlons du mot concis , qui caractérise le style fonctionnel du code utilisant des expressions lambda
Dans ce contexte, le code se veut concis, sans fioritures, et réduit à un impact direct pour transmettre plus efficacement l'intention. Ce sont des avantages considérables. Écrire du code, c'est comme assembler des ingrédients : le rendre concis , c'est comme y ajouter de la sauce. Parfois, il faut plus d’efforts pour écrire un tel code. Moins de code à lire, mais cela rend le code plus transparent. Il est important de garder le code clair lorsque vous le raccourcissez. Un code concis s’apparente à des astuces de conception. Ce code nécessite moins de danser avec un tambourin. Cela signifie que nous pouvons rapidement mettre en œuvre nos idées et passer à autre chose si elles fonctionnent et les abandonner si elles ne répondent pas aux attentes.
Itérations sous stéroïdes
Nous utilisons des itérateurs pour traiter des listes d'objets, ainsi que pour travailler avec des ensembles et des cartes. Les itérateurs que nous utilisons en Java nous sont familiers ; bien qu’ils soient primitifs, ils ne sont pas simples. Non seulement ils occupent un certain nombre de lignes de code, mais ils sont également assez difficiles à écrire. Comment parcourir tous les éléments d’une collection ? Nous pourrions utiliser une boucle for. Comment sélectionnons-nous certains éléments de la collection ? Utiliser la même boucle for, mais en utilisant des variables mutables supplémentaires qui doivent être comparées à quelque chose de la collection. Ensuite, après avoir sélectionné une valeur spécifique, comment pouvons-nous effectuer des opérations sur une valeur unique, telle qu'une valeur minimale, maximale ou une valeur moyenne ? Encore des cycles, encore de nouvelles variables. Cela n’est pas sans rappeler le proverbe, on ne voit pas les arbres à cause de la forêt (l’original utilise un jeu de mots lié aux itérations et signifie « Tout s’assume, mais tout ne réussit pas » - ndlr). jdk fournit désormais des itérateurs internes pour diverses instructions : un pour simplifier le bouclage, un pour lier la dépendance du résultat requise, un pour filtrer les valeurs de sortie, un pour renvoyer les valeurs et plusieurs fonctions pratiques pour obtenir le minimum, le maximum, les moyennes, etc. De plus, les fonctionnalités de ces opérations peuvent être combinées très facilement, de sorte que nous pouvons en combiner différents ensembles pour implémenter la logique métier avec plus de facilité et avec moins de code. Lorsque nous aurons terminé, le code sera plus facile à comprendre car il crée une solution logique dans l'ordre requis par le problème. Nous examinerons quelques exemples d'un tel code au chapitre 2 et plus loin dans ce livre.
Application d'algorithmes
Les algorithmes pilotent les applications d'entreprise. Par exemple, nous devons fournir une opération qui nécessite une vérification d'autorité. Nous devrons nous assurer que les transactions soient complétées rapidement et que les contrôles soient effectués correctement. De telles tâches sont souvent réduites à une méthode très ordinaire, comme dans la liste ci-dessous : Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Cette approche pose deux problèmes. Premièrement, cela conduit souvent à un doublement de l’effort de développement, ce qui entraîne à son tour une augmentation du coût de maintenance de l’application. Deuxièmement, il est très facile de manquer les exceptions qui peuvent être générées dans le code de cette application, compromettant ainsi l'exécution de la transaction et le passage des contrôles. Nous pouvons utiliser un bloc try-finally approprié, mais chaque fois que quelqu'un touche ce code, nous devrons revérifier que la logique du code n'a pas été brisée. Sinon, nous pourrions abandonner l’usine et renverser tout le code. Au lieu de recevoir des transactions, nous pourrions envoyer du code de traitement à une fonction bien gérée, comme le code ci-dessous. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Ces petits changements génèrent d'énormes économies. L'algorithme de vérification de l'état et de vérification des applications bénéficie d'un nouveau niveau d'abstraction et est encapsulé à l'aide de la méthode runWithinTransaction() . Dans cette méthode, nous plaçons un morceau de code qui doit être exécuté dans le contexte d'une transaction. Nous n’avons plus à nous soucier d’oublier de faire quelque chose ou de savoir si nous avons détecté l’exception au bon endroit. Les fonctions algorithmiques s’en chargent. Cette question sera abordée plus en détail au chapitre 5.
Extensions d'algorithme
Les algorithmes sont de plus en plus utilisés, mais pour qu'ils soient pleinement utilisés dans le développement d'applications d'entreprise, des moyens de les étendre sont nécessaires.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION