Fatigué des exceptions de pointeur Null? Évaluez la possibilité d’utiliser Java optionnel SE8.

Un homme très sage l’a dit une fois qu’un véritable programmeur Java n’a pas été confronté à une exception du pointeur Null. Blagues de côté, la référence NULL donne lieu à de nombreux problèmes car il est souvent utilisé pour désigner l’absence d’une valeur. Java SE8 présente une nouvelle classe appelée Java.Util.Optional qui peut atténuer certains de ces problèmes.

Commençons par un exemple pour voir les dangers d’utiliser NULL. Pensez, par exemple, dans une structure d’objets imbriqués pour représenter un ordinateur, comme illustré à la figure 1.

Java8 -Optional - Fig.1

Figure 1: Structure imbriquée pour représenter l’ordinateur

quels problèmes peuvent-ils présenter le code suivant?

String version = computer.getSoundcard().getUSB().getVersion();

Le code semble assez logique. Cependant, de nombreux ordinateurs (par exemple, les PI de framboise) sont distribués sans carte son. Donc, quel est le résultat de getoundcard ()?

une pratique courante consiste à renvoyer la référence null pour indiquer l’absence de carte son. Malheureusement, cela signifie que l’appel à GetUSB () tentera de retourner le port USB d’une référence null; Par conséquent, une exception NullpointerException sera lancée au moment de l’exécution et le programme cessera de fonctionner. Imaginez que votre programme fonctionnait sur l’équipe d’un client: qu’est-ce que ce client dirait si le programme, soudainement, a échoué?

Pour fournir un peu de contexte historique, Tony Hoare -One des géants de l’informatique – Il a écrit: « J’appelle mon erreur de milliards de dollars: l’invention de la référence null en 1965. Je ne pouvais pas résister à la tentation d’insérer une référence null. C’était si facile de le mettre en œuvre … ».

Que peut-on faire pour éviter des exceptions de pointeur NULL non intentionnelles? Une attitude défensive peut être adoptée et ajouter des chèques pour éviter les références nulelles, comme indiqué dans la liste 1:

Liste 1

Cependant, il est facile de voir que tout de suite la liste 1 commence à perdre de l’élégance en raison des chèques imbriqués. Malheureusement, nous avons besoin de beaucoup de code répétitif pour vous assurer de ne pas obtenir une erreur NullpointerException. En outre, il est gênant que ces chèques interfèrent avec la logique des entreprises. En fait, ils réduisent la lisibilité générale du programme.

En outre, il s’agit d’un processus sujet à des erreurs: que se passerait-il si vous avez oublié de vérifier qu’une propriété peut être nulle? Dans cet article, je dirai que l’utilisation de NULL pour représenter l’absence de valeur constitue une approche erronée. Ce dont nous avons besoin, c’est un meilleur moyen de modéliser l’absence et la présence d’une valeur.

Pour fournir un contexte à l’analyse, examinez quelles autres langues de programmation.

qui Sont les alternatives à l’utilisation de NULL?

Langues comme Groovy ont un opérateur de navigation sécurisé représenté par « ? » Surmonter les références potentiellement nulles sans risque. (Notez que c # aura également cet opérateur et qu’il avait été proposé de l’inclure dans Java 7, bien qu’il n’ait pas été possible de le faire dans cette version.) Cela fonctionne comme suit:

String version = computer?.getSoundcard()?.getUSB()?.getVersion();

Dans ce cas, une valeur nulle sera attribuée à une valeur null si l’ordinateur est NULL ou getoundCard () renvoie NULL ou GETUSB () Retturve Null. Il n’est pas nécessaire d’introduire des conditions imbriquées complexes pour vérifier la présence de NULL. De plus, Groovy a également l’opérateur Elvis « ?: » (Si vous le regardez, vous reconnaîtrez la célèbre coiffure Elvis), qui peut être utilisée pour des cas simples lorsqu’une valeur par défaut est requise. Dans l’exemple suivant, si l’expression qui utilise l’opérateur de navigation sécurisé renvoie NULL, la valeur par défaut « inconnue » est renvoyée; Dans le cas contraire, l’étiquette est renvoyée avec la version disponible.

String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN";

Autres langues fonctionnelles, telles que HASKELL et SCALA, adoptent une vision différente. Haskell comprend un type peut-être, qui, fondamentalement, encapsule une valeur optionnelle. Une valeur du type peut-être peut contenir une valeur d’un type donné ou de rien. Il n’y a pas de concept de référence null. Scala a une construction similaire appelée option pour encapsuler la présence ou l’absence d’une valeur de type T. Ensuite, il est nécessaire de vérifier explicitement si une valeur est présente ou absente à l’aide d’opérations disponibles dans le type d’option, ce qui rend la « chèque de null ». . Il n’est plus possible de « oublier de vérifier » car le système de type ne le permet pas.Eh bien, nous avons un peu détourné le sujet et tout cela semble assez résumé. Peut-être qu’ils se demandent: « Alors, que propose Java 8? ».

Facultatif en quelques mots

Java est 8 inclut une nouvelle classe appelée Java.Util.Optional < t >, inspiré par haskell et scala. Il s’agit d’une classe qui encapsule une valeur facultative, comme indiqué dans la liste 2, inclus ci-dessous et sur la figure 2. Facultatif peut être considéré comme un conteneur de valeur unique qui contient une valeur ou ne la contient pas (dans ce cas, il est dit. qu’il est « vide »), comme illustré à la figure 2. Java8 -Optional- fig.2

Figure 2: Son optionnel de carte

Nous pouvons modifier notre modèle et utiliser facultatif, comme il est observé dans la liste 2:

Liste 2

Dans la liste 2, il est immédiatement évident qu’un ordinateur peut ou non avoir une carte son (la carte son est facultative). De plus, la carte son peut éventuellement compter avec le port USB. C’est une amélioration par rapport au modèle précédent, car ce nouveau modèle reflète clairement si une certaine valeur est autorisée à être absente. Notez que des possibilités similaires sont disponibles dans les bibliothèques comme GUA. Mais que peut-on réellement être fait avec un objet en option < SoundCard >? Après tout, vous souhaitez obtenir le numéro de version du port USB. En bref, la classe facultative comprend des méthodes permettant de traiter explicitement des cas dans lesquels une valeur est présente ou absente. Toutefois, l’avantage comparé aux références nuls est que la classe facultative oblige à penser à la cause dans laquelle la valeur n’est pas présente. En conséquence, il est possible d’empêcher des exceptions de pointeur nulle non intentionnelles. Il est important de noter que l’objectif de la classe facultative n’est pas de remplacer toutes les références nulles, mais son objectif est d’aider à concevoir des routines d’API plus compréhensibles, telles que la lecture de la signature d’une méthode peut être possible si le remboursement peut être attendu. d’une valeur optionnelle. Si tel est le cas, nous sommes obligés de « désenconable » la classe facultative d’agir avant l’absence de valeur.

Motifs de l’adoption de facultative

Explication suffisante: allons-y Pour coder. Premièrement, nous verrons comment réécrire les modèles de vérification NULL typiques en option. À la fin de cet article, vous saurez utiliser facultatif pour réécrire le code indiquant la liste 1, dans laquelle plusieurs chèques nuls nuls nuls ont été effectués:

String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

Remarque: assurez-vous de revoir la syntaxe des références aux méthodes et expressions Lambda à partir de Java SE8 (voir «Java 8: Lambdas») ainsi que les concepts de canalage (pipelining) à partir de flux (voir « Les données de traitement avec Java sont 8 flux »).

Création d’objets en option

Premièrement, comment sont créés des objets optionnels? Il existe plusieurs modes: il s’agit d’un vide optionnel:

Optional&lt;Soundcard&gt; sc = Optional.empty();

et ceci est une option avec une valeur non nulle:

SoundCard soundcard = new Soundcard();Optional<Soundcard> sc = Optional.of(soundcard); 

Soundcard était NULL, une exception NullpointerException serait immédiatement lancée (au lieu d’obtenir une erreur latente lors de la tentative d’accès des propriétés de carte son). En outre, en utilisant des aspérons, il est possible de créer un objet facultatif pouvant contenir une valeur null:

Optional<Soundcard> sc = Optional.ofNullable(soundcard);

Si la carte son était nulle, l’objet facultatif résultant serait vide.

faire quelque chose avant la présence d’une valeur

Maintenant que nous avons un objet facultatif, nous pouvons recourir aux méthodes disponibles pour traiter explicitement de la présence ou de l’absence de valeurs. Au lieu de nous voir obligé de nous rappeler de vous rappeler de vérifier à NULL, comme suit:

Nous pouvons utiliser la méthode IFPSENT () vu ci-dessous:

« 098b24877f »>

Nous n’avons plus besoin d’exécuter une chèque null explicite: le système de types qui prend soin de l’exécuter . Si l’objet en option était vide, rien ne serait imprimé. Nous pouvons également utiliser la méthode ispresent () pour savoir s’il ya une valeur sur un objet facultatif. De plus, il existe une méthode get () qui renvoie la valeur contenue dans l’objet facultatif, le cas échéant. Sinon, il jette une exception NoschelementException. Il est possible de combiner les deux méthodes pour prévenir les exceptions, comme indiqué ci-dessous:

« 16aee90ff4 »>

Cependant, ce n’est pas cette utilisation recommandée de facultatif (ne représente pas une amélioration significative par rapport aux chèques nichés nuls); De plus, il existe plus de solutions de remplacement linguistique que nous allons explorer plus tard.

Valeurs et actions par défaut

Un modèle typique consiste à renvoyer une valeur par défaut s’il est déterminé que le résultat d’une opération est null. En général, pour atteindre cet objectif, l’opérateur ternaire peut être utilisé:

Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");

Si un objet optionnel est utilisé, il est utilisé possible de réécrire le code précédent à l’aide de la méthode Orelse (), qui fournit une valeur par défaut si facultatif est vide:

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut")); 

[) peut être utilisé, que, au lieu de fournir une valeur prédéterminée en option, est vide, elle jette une exception:

Soundcard soundcard = maybeSoundCard.orElseThrow(IllegalStateException::new);

Rejet de Certaines valeurs avec l’utilisation de la méthode de filtrage

souvent, il est nécessaire d’appeler une méthode d’objet et de vérifier certaines propriétés. Par exemple, il peut être nécessaire de vérifier si le port USB est une certaine version. Pour ce faire sans risque, il est nécessaire de vérifier d’abord si la référence qui pointe sur un objet USB est null et appeler, puis la méthode GetVersion (), comme suit:

« B586B99510 «  »>

Il est possible de réécrire ce motif à l’aide de la méthode de filtrage pour un objet facultatif, comme indiqué ci-dessous:

/ div>

La méthode du filtre prend un prédicat comme un argument. S’il y a une valeur dans l’objet facultatif et que la valeur est conforme au prédicat, la méthode du filtre renvoie cette valeur; Sinon, il renvoie un objet optionnel vide. Vous pouvez trouver un modèle similaire si vous avez utilisé la méthode de filtrage avec l’interface de flux.

retrait et transformation des valeurs avec la méthode MAP

est un autre modèle fréquent consiste à extraire informations d’un objet. Par exemple, il peut arriver que vous souhaitiez extraire l’objet USB d’un objet de carte son et vérifiez, alors s’il provient de la version correcte. Le code typique serait:

if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null && "3.0".equals(usb.getVersion()){ System.out.println("ok"); }}

Il est possible de réécrire ce modèle « Vérifier null et d’extraire » (dans ce cas, La carte son d’objet) à l’aide de la méthode de la carte.

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);

Il y a un parallèle direct avec la méthode de la carte utilisée avec des flux. Là, une fonction est transmise à la méthode de la carte, qui applique cette fonction à chaque élément d’un flux. Cependant, si le flux est vide, rien ne se passe. La méthode de la carte de la classe facultative fait la même chose: la fonction qui est transmise sous forme d’argument (dans ce cas, une référence à un procédé d’extraction du port USB) « transformer » la valeur contenue en option, tandis que rien ne se passe si facultatif est vider. Enfin, nous pouvons combiner la méthode de la carte avec la méthode de filtrage pour rejeter un port USB dont la version n’est pas 3.0:

super : Notre code commence à approcher de la cible recherché, sans vérifications nulles explicites interposées sur la route.

eau d’objet optionnelle avec la méthode Flatmap

Nous avons déjà vu des modèles qu’ils peuvent être refactorisé pour utiliser facultatif. Maintenant, comment pouvons-nous écrire le code de mode de sécurité suivant?

String version = computer.getSoundcard().getUSB().getVersion();

Notez que la seule fonction de ce code consiste à extraire un objet d’un autre, exactement le but de la méthode de la carte. Dans les lignes précédentes, nous modifions notre modèle de sorte que l’ordinateur avait la facultative < et la carte son dispose en option < USB >, il devrait donc être possible d’écrire ce qui suit:

String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

Malheureusement, il n’est pas possible de compiler ce code. Parce que? La variable d’ordinateur est optionnelle type < ordinateur >, il est donc correct d’appeler la méthode de la carte. Cependant, getoundcard () renvoie un objet de type optionnel < SoundCard >, ce qui signifie que le résultat de l’opération de carte est un objet de facultatif Tapez < en option < SOUNDCARD > > En conséquence, l’appel à getSB () n’est pas valide car l’option extérieure contient une autre option que, bien sûr, ne prend pas en charge la méthode GetUsb (). La figure 3 illustre la structure électorale imbriquée qui serait obtenue. java8 -Optional- fig.3

Figure 3: Un autre niveau optionnel

Comment peut-on résoudre le problème? Une fois encore, nous pouvons recourir à un modèle que vous avez peut-être utilisé auparavant avec des flux: la méthode FlatMap. Avec les flux, la méthode Flatmap prend une fonction comme argument, qui renvoie un nouveau flux. Cette fonction est appliquée à chaque élément de flux, ce qui entraînerait un flux de flux.Cependant, l’effet Flatmap consiste à remplacer chaque courant généré par le contenu du flux en question. En d’autres termes, tous les flux générés par la fonction sont amalgaman ou « aplanan » dans un seul flux. Ce dont nous avons besoin dans ce cas est quelque chose de similaire, mais nous cherchons « aplatir » un niveau optionnel deux niveaux et obtenir, d’autre part, un seul niveau.

Eh bien, nous avons de bonnes nouvelles: facultatif également en charge une méthode Flatmap. Son objectif est d’appliquer le processus de transformation à la valeur d’un facultatif (comme cela se produit avec l’opération de carte), puis « aplatir » les deux niveaux facultatifs pour obtenir un seul niveau. La figure 4 illustre la différence entre la carte et la carte routière lorsque la fonction de transformation renvoie un objet en option.

Java8 -Optional- fig.4

Figure 4: Comparaison du Utilisation de la carte et de la carte de colonne avec facultatif

puis, de sorte que le code est correct, nous devons la réécrire comme suit à l’aide de PLATMAP:

String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

La première place garantie qu’il est renvoyé en option < SOUNDCARD > au lieu de la facultative < en option < SOUNDCARD > > et le second Atteint le même objectif avec le retour de la facultative < USB >. Notez que dans le cas du troisième appel, seule carte () est nécessaire car GetVersion () renvoie une chaîne à la place d’un objet facultatif.

génial! Nous avons fait beaucoup de progrès: nous sommes allés d’écrire des dames nulles ennuyeux pour écrire un code déclaratif lisible, prend en charge la composition et est mieux protégé des exceptions de pointeur NULL.

Conclusion

Dans cet article, nous avons adressé l’adoption de la nouvelle classe Java.Util.Optional < T > Java SE 8. Le but de En option ne remplace pas toutes les références nuls du code, mais pour aider à concevoir de meilleures routines d’API dans lesquelles, en lisant la signature d’une méthode, les utilisateurs savent s’il faut ou non attendre une valeur facultative. En outre, option oblige la « désencapulation » une option afin d’agir en l’absence d’une valeur; En conséquence, la présence d’exceptions de pointeur nulées non intentionnelles est empêchée dans le code.

Informations supplémentaires

  • chapitre 9, « Facultatif: une meilleure alternative à NULL » (Facultatif, une meilleure alternative que NULL), en JAVA 8 en action: Lambdas, flux et programmation de style fonctionnel (Java 8 en action: Lambdas, flux et programmation fonctionnelle)
  • « Monadic Java » ( Java Monadic) Par Mario Fusco
  • « Traitement des données avec Java 8 flux » (traitement des données avec Java Streams SE8)

Remerciements

Je remercie Alan Mycroft et Mario Fusco pour se lancer dans l’écriture de Java 8 en action: Lambdas, des ruisseaux et une programmation de style fonctionnel avec moi.

Raoul-Gabriel Urma (@raouluk) finit son doctorat en informatique à L’Université de Cambridge, où il développe ses recherches dans les langages de programmation. C’est Coauthor de Java 8 en action: Lambdas, flux et programmation de style fonctionnel, qui sera publié bientôt par Manning. En outre, il participe généralement comme un exposant à des conférences sur la première ligne Java (par exemple, Devoxx et Fosdem) et est effectuée en tant qu’instructeur. Il a également travaillé dans plusieurs sociétés prestigieuses, notamment l’équipe de Google Python, le groupe de plate-forme Java d’Oracle, eBay et Goldman Sachs, ainsi que dans plusieurs projets de nouvelles entreprises.

Leave a Comment

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *