Le livre "Java moderne. Expressions Lambda, flux et programmation fonctionnelle »

image Salut, habrozhiteli! L'avantage des applications modernes réside dans les solutions avancées, notamment les microservices, les architectures réactives et le streaming de données. Les expressions lambda, les flux de données et le système tant attendu de modules de la plate-forme Java simplifient considérablement leur mise en œuvre.

Le livre vous aidera à découvrir de nouvelles fonctionnalités des modules complémentaires modernes, tels que l'API Streams et le système de modules de la plate-forme Java. Découvrez de nouvelles approches de la compétitivité et découvrez comment les concepts de fonctionnalité améliorent le travail avec le code.

Dans ce livre: • Nouvelles fonctionnalités Java • Streaming de données et programmation réactive • Système de modules de plateforme Java.

Extrait. Chapitre 11. La classe facultative comme meilleure alternative à null


Levez la main si, au cours de votre carrière de développeur Java, vous avez déjà reçu une exception NullPointerException. Laissez-la levée si cette exception est la plus courante que vous ayez rencontrée. Malheureusement, nous ne vous voyons pas maintenant, mais il est très probable que votre main soit levée. Nous pensons également que vous pensez peut-être à quelque chose comme: «Oui, je suis d'accord. Exceptions NullPointerException est un casse-tête pour tout développeur Java, qu'il soit débutant ou expert. Mais rien ne peut être fait avec eux de toute façon, c'est le prix que nous payons pour utiliser un design aussi pratique et probablement inévitable que les liens vides. » Il s'agit d'une opinion commune dans le monde de la programmation (impérative); néanmoins, ce n'est peut-être pas toute la vérité, mais plutôt un préjugé profondément enraciné.

Le spécialiste britannique de l'informatique Tony Hoare, qui a créé des liens nuls en 1965, tout en développant le langage ALGOL W, l'un des premiers langages de programmation typés avec des enregistrements pour lesquels de la mémoire était allouée sur le tas, a admis plus tard qu'il avait fait cela " juste à cause de la facilité de mise en œuvre. " Bien qu'il veuille garantir "la sécurité totale de l'utilisation d'une exception pour les liens vides, il pensait que c'était le moyen le plus pratique pour simuler l'absence de valeur. Plusieurs années plus tard, il a regretté cette décision, l'appelant "mon erreur d'un milliard de dollars". Nous avons tous vu les résultats de cette décision. Par exemple, nous pouvons vérifier le champ d'un objet pour déterminer s'il représente l'une des deux valeurs possibles, pour constater que nous vérifions non pas un objet, mais un pointeur nul, et obtenir immédiatement cette NullPointerException agaçante.

En fait, Hoar a peut-être sous-estimé le coût de la réparation de millions de développeurs d'erreurs causées par des liens vides au cours des 50 dernières années. En effet, la grande majorité des langages de programmation1 créés au cours des dernières décennies, y compris Java, sont basés sur la même décision de conception, peut-être pour des raisons de compatibilité avec les langages plus anciens ou (plus probablement), comme l'a dit Hoar, «simplement en raison de la facilité de mise en œuvre ". Nous commençons par démontrer un exemple simple des problèmes rencontrés lors de l'utilisation de null.

11.1. Comment simuler un manque de valeur


Imaginez que vous ayez la structure d'objet imbriquée dans l'extrait 11.1 pour le propriétaire d'une voiture qui a acheté une assurance automobile.

Listing 11.1. Modèle de données personne / voiture / assurance

public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } 

Selon vous, quel est le problème avec le code suivant?

 public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); } 

Ce code semble assez raisonnable, mais beaucoup de gens n'ont pas de voiture, alors quel est le résultat de l'appel de la méthode getCar dans ce cas? Souvent (et en vain), ils renvoient un lien vide pour indiquer l'absence d'une valeur (dans ce cas, pour indiquer l'absence d'une machine). Par conséquent, l'appel de la méthode getInsurance renverra une assurance de lien vide, ce qui provoquera une exception NullPointerException au moment de l'exécution et l'arrêt du programme. Mais ce n'est pas tout. Mais que faire si l'objet personne était nul? Que se passe-t-il si getInsurance retourne également null?

11.1.1. Réduisez NullPointerException avec Security Check


Comment éviter une exception NullPointerException inattendue? Vous pouvez généralement ajouter des contrôles nuls partout où vous en avez besoin (et parfois, dépasser les exigences d'une programmation sûre et là où vous n'en avez pas besoin), et souvent dans des styles différents. Notre première tentative d'écrire une méthode pour empêcher la génération d'une NullPointerException est montrée dans le Listing 11.2.

image

Cette méthode vérifie la valeur null chaque fois qu'une variable est déréférencée, renvoyant la valeur de chaîne "Unknown" si au moins une des variables rencontrées dans cette chaîne de déréférencement est une valeur vide. La seule exception à cette règle est que nous ne vérifions pas le nom de la compagnie d'assurance pour null, car nous savons que (comme toute autre compagnie), elle doit avoir un nom. Veuillez noter que nous parvenons à éviter cette dernière vérification uniquement en raison de la connaissance du domaine, mais ce fait ne se reflète pas dans les classes Java qui modélisent nos données.

Nous avons décrit la méthode du Listing 11.2 comme «des doutes profonds», car un motif répétitif y est perceptible: chaque fois dans le doute, la variable n'est pas nulle, vous devez ajouter un autre bloc if imbriqué, augmentant ainsi le niveau de retrait du code. De toute évidence, cette technique n’est pas très évolutive et réduit la lisibilité, il est donc préférable d’essayer une solution différente. Pour éviter ce problème, prenons un chemin différent, comme indiqué dans l'extrait 11.3.

Dans la deuxième tentative, nous essayons d'éviter l'imbrication profonde des blocs if en utilisant une stratégie différente: chaque fois que nous tombons sur une variable nulle, nous renvoyons la valeur de chaîne "Unknown". Mais cette solution est également loin d'être idéale; Maintenant, la méthode a quatre points de sortie différents, ce qui complique grandement sa maintenance. De plus, la valeur par défaut retournée dans le cas de null - la chaîne "Unknown" - est répétée à trois endroits et (nous l'espérons) ne contient pas de fautes d'orthographe! Pour éviter cela, vous pouvez bien sûr déplacer la chaîne répétitive vers une constante.

image

De plus, ce processus est sujet aux erreurs. Que faire si vous oubliez de vérifier si l'une des propriétés est nulle? Dans ce chapitre, nous soutenons que l'utilisation de null pour représenter un manque de valeur est une approche fondamentalement erronée. Une meilleure façon de modéliser l'absence et la présence de valeur est nécessaire.

11.1.2. Problèmes rencontrés avec null


Pour résumer ce qui précède, l'utilisation de liens vides en Java conduit aux problèmes suivants, théoriques et pratiques.

  • Sert de source d'erreurs. NullPointerException est l'exception la plus courante (par une large marge) en Java.
  • "Gonfle" le code. La lisibilité s'aggrave en raison de la nécessité de remplir le code avec des contrôles nuls, souvent profondément imbriqués.
  • Cela n'a aucun sens. Il manque tout sens sémantique, en particulier, une telle approche pour modéliser l'absence de sens dans une langue à typification statique est fondamentalement erronée.
  • Violent l'idéologie du langage Java. Java cache toujours des pointeurs aux développeurs, à une exception près: un pointeur nul.
  • Crée un espace dans le système de texte. null n'inclut aucun type ni aucune autre information, il peut donc être attribué à n'importe quel type de lien. Cela peut entraîner des problèmes lors du transfert de null vers une autre partie du système où il n'y a aucune information sur ce que ce null doit être initialement.

Comme base pour d'autres solutions possibles dans la sous-section suivante, nous examinons brièvement les possibilités offertes par d'autres langages de programmation.

11.1.3. Alternatives à null dans d'autres langages de programmation


Ces dernières années, des langages tels que Groovy ont réussi à contourner ce problème en introduisant un opérateur d'appel sûr (?.), Conçu pour fonctionner en toute sécurité avec des valeurs nulles potentielles. Pour comprendre comment ce processus est implémenté dans la pratique, considérez le code Groovy suivant. Il récupère le nom de la compagnie d'assurance dans laquelle la personne spécifiée a assuré la voiture:

def carInsuranceName = person?.car?.insurance?.name

Il devrait être clair pour vous ce que fait ce code. Une personne peut ne pas avoir de voiture, pour la simulation de laquelle nous attribuons null au lien voiture de l'objet personne. De même, une machine peut être non assurée. L'opérateur d'appel sécurisé de Groovy vous permet de travailler en toute sécurité avec des liens potentiellement vides sans lever une exception NullPointerException, en passant le lien vide le long de la chaîne d'appel et en renvoyant null si une valeur de la chaîne est nulle.

Une fonctionnalité similaire a été proposée pour être implémentée dans Java 7, mais il a ensuite été décidé de ne pas le faire. Cependant, curieusement, l'opérateur d'appel sécurisé n'est pas vraiment nécessaire en Java. La première pensée de tout développeur Java lorsqu'il rencontre une NullPointerException est de résoudre rapidement le problème en ajoutant une instruction if pour vérifier la valeur null avant d'appeler sa méthode. Résoudre le problème de manière similaire, sans considérer si null est acceptable dans cette situation particulière pour votre algorithme ou modèle de données, ne conduit pas à une correction, mais à masquer l'erreur. Et par la suite pour le prochain développeur (probablement vous-même dans une semaine ou un mois), il sera beaucoup plus difficile de trouver et de corriger cette erreur. En fait, vous venez de balayer la poubelle sous le tapis. L'opérateur de déréférencement à sécurité nulle de Groovy n'est qu'un balai plus grand et plus puissant, avec lequel vous pouvez faire de telles bêtises sans vraiment vous soucier des conséquences.

D'autres langages de programmation fonctionnels, tels que Haskell et Scala, considèrent ce problème différemment. Haskell a un type Maybe, qui encapsule essentiellement une valeur facultative. Un objet de type Peut-être peut contenir une valeur du type spécifié ou ne rien contenir. Haskell n'a pas le concept d'un lien vide. Dans Scala, pour encapsuler la présence ou l'absence d'une valeur de type T, une structure logique similaire Option [T] est fournie, dont nous parlerons au chapitre 20. Dans ce cas, vous devez explicitement vérifier si une valeur est présente à l'aide d'opérations de type Option, qui fournissent des «vérifications nulles» . Désormais, il n'est plus possible «d'oublier de vérifier la valeur nulle», car le système de type lui-même nécessite une vérification.

D'accord, nous sommes un peu hors sujet, et tout cela semble assez abstrait. Vous vous demandez probablement ce que Java 8 offre dans ce sens. Inspirés par l'idée d'une valeur optionnelle, les créateurs de Java 8 ont introduit une nouvelle classe, java.util.Optional! Dans ce chapitre, nous montrons quel est l'avantage de l'utiliser pour modéliser des valeurs potentiellement manquantes au lieu de leur attribuer une référence vide. Nous expliquerons également pourquoi une telle transition de null à Facultatif nécessite que le programmeur revoie l'idéologie du travail avec des valeurs facultatives dans le modèle de domaine. Enfin, nous explorerons les possibilités de cette nouvelle classe facultative et fournirons quelques exemples pratiques de son utilisation efficace. En conséquence, vous apprendrez à concevoir des API améliorées, dans lesquelles l'utilisateur comprend déjà à partir de la signature de la méthode si une valeur facultative est possible ici.

11.2. Présentation de la classe facultative


En Java 8, sous l'influence des langages Haskell et Scala, une nouvelle classe java.util.Optional a semblé encapsuler une valeur facultative. Par exemple, si vous savez qu'une personne peut posséder ou non une voiture, vous ne devez pas déclarer la variable car dans la classe Person avec le type Car et lui affecter une référence vide si la personne n'a pas de voiture; au lieu de cela, son type doit être facultatif, comme le montre la fig. 11.1.

image

Étant donné une valeur, la classe Facultatif lui sert d'adaptateur. Inversement, l'absence de valeur est modélisée à l'aide de l'option facultative vide renvoyée par la méthode Optional.empty. Cette méthode de fabrique statique renvoie une seule instance spéciale de la classe Optional. Vous vous demandez peut-être quelle est la différence entre un lien vide et Optional.empty (). Sémantiquement, ils peuvent être considérés comme une seule et même chose, mais dans la pratique, il existe d'énormes différences entre eux. Une tentative de déréférencer null conduit inévitablement à une exception NullPointerException, et Optional.empty () est un objet valide et réalisable de type Optional, auquel on peut facilement accéder. Bientôt, vous verrez comment exactement.

Une différence sémantique pratique importante dans l'utilisation d'objets facultatifs au lieu de null: déclarer une variable de type Facultatif indique clairement qu'une valeur vide est autorisée à ce stade. Et vice versa, en utilisant toujours le type Car et, éventuellement, en attribuant parfois des références vides à des variables de ce type, vous voulez dire que vous ne comptez que sur vos connaissances de domaine pour comprendre si null appartient au domaine de définition d'une variable donnée.

Dans cet esprit, vous pouvez refaire le modèle d'origine du Listing 11.1. Nous utilisons la classe Optional, comme indiqué dans l'extrait 11.4.

Notez que l'utilisation de la classe Optional enrichit la sémantique du modèle. Un champ Facultatif dans la classe Personne et un champ Facultatif dans la classe Voiture reflètent le fait qu'une personne peut ou non avoir une voiture, tout comme une machine peut ou non être assurée.

Dans le même temps, le fait de déclarer le nom de la compagnie d'assurance en tant que chaîne plutôt qu'en option indique clairement que la compagnie d'assurance doit avoir un nom. Ainsi, vous savez avec certitude que vous obtiendrez une NullPointerException lorsque vous déréférencerez le nom de la compagnie d'assurance; l'ajout d'une vérification nulle n'est pas nécessaire, car cela ne ferait que masquer le problème. La compagnie d'assurance doit avoir un nom, donc si vous rencontrez une entreprise sans nom, vous devez découvrir ce qui ne va pas avec les données et ne pas ajouter de code pour masquer ce fait.

image

L'utilisation cohérente de valeurs facultatives crée une distinction claire entre une valeur qui peut être absente et une valeur manquante en raison d'une erreur dans l'algorithme ou d'un problème dans les données. Il est important de noter que la classe Optional n'est pas destinée à remplacer tous les liens vides par un seul. Sa tâche est d'aider à concevoir des API plus compréhensibles afin que par la signature de la méthode, on puisse comprendre si une valeur facultative peut y être trouvée. Le système de type Java force une option de décompression pour gérer l'absence de valeur.

À propos des auteurs


Raul-Gabriel Urma est le PDG et co-fondateur de Cambridge Spark (Royaume-Uni), une communauté éducative de premier plan pour les chercheurs et développeurs de données. Raul a été élu l'un des participants au programme Java Champions en 2017. Il a travaillé pour Google, eBay, Oracle et Goldman Sachs. Il a soutenu sa thèse en génie informatique à l'Université de Cambridge. En outre, il détient une maîtrise en ingénierie de l'Imperial College de Londres, a obtenu son diplôme avec distinction et a reçu plusieurs prix pour des propositions de rationalisation. Raul a présenté plus de 100 rapports techniques lors de conférences internationales.

Mario Fusco est ingénieur logiciel senior chez Red Hat, impliqué dans le développement du noyau Drools, le moteur de règles JBoss. Il possède une riche expérience en développement Java, a participé (souvent en tant que développeur de premier plan) à de nombreux projets d'entreprise dans diverses industries, des médias au secteur financier. Parmi ses intérêts figurent la programmation fonctionnelle et les langages spécifiques au domaine. Sur la base de ces deux hobbies, il a créé la bibliothèque open source lambdaj, souhaitant développer un DSL Java interne pour travailler avec des collections et permettre d'utiliser certains éléments de programmation fonctionnelle en Java.

Alan Mycroft est professeur au Département d'informatique de l'Université de Cambridge, où il enseigne depuis 1984. Il est également un employé de Robinson College, l'un des fondateurs de l'Association européenne des langages et systèmes de programmation, et l'un des fondateurs et administrateurs de la Fondation Raspberry Pi. Il est diplômé en mathématiques (Cambridge) et en informatique (Edinburgh). Alan est l'auteur de plus de 100 articles scientifiques. Il était le superviseur de plus de 20 doctorants. Ses recherches portent principalement sur le domaine des langages de programmation et leur sémantique, l'optimisation et l'implémentation. Pendant un certain temps, il a travaillé aux laboratoires AT&T et au département de recherche d'Intel, et a également co-fondé Codemist Ltd., qui a publié le compilateur de langage C pour l'architecture ARM, appelé Norcroft.

»Plus d'informations sur le livre sont disponibles sur le site Web de l'éditeur
» Contenu
» Extrait

25% de réduction sur les colporteurs - Java

Lors du paiement de la version papier du livre, un livre électronique est envoyé par e-mail.

Source: https://habr.com/ru/post/fr470598/


All Articles