Pourquoi utiliser Facultatif dans Java 8+ au lieu des contrôles de pointeur null traditionnels?

110

Nous avons récemment migré vers Java 8. Maintenant, je vois des applications inondées d’ Optionalobjets.

Avant Java 8 (Style 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

Après Java 8 (Style 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

Je ne vois aucune valeur ajoutée Optional<Employee> employeeOptional = employeeService.getEmployee();lorsque le service lui-même renvoie facultatif:

Venant d'un environnement Java 6, je vois le style 1 comme plus clair et avec moins de lignes de code. Y at-il un avantage réel qui me manque ici?

Consolidé à partir de la compréhension de toutes les réponses et des recherches plus poussées sur le blog

utilisateur3198603
la source
20
Oracle a publié un article complet sur l'utilisation de facultatif .
Mike Partridge
28
Beurk! employeeOptional.isPresent()semble manquer complètement le point de types d'options. Selon le commentaire de @ MikePartridge, cela n'est ni recommandé ni idiomatique. Vérifier explicitement si un type d'option est quelque chose ou rien est un non-non. Vous êtes censé simplement mapou flatMapsur eux.
Andres F.
7
Voir une réponse au débordement de pileOptional de Brian Goetz , architecte des langages Java chez Oracle.
Basil Bourque
6
C'est ce qui se passe lorsque vous éteignez votre cerveau au nom des "meilleures pratiques".
user253751
9
C’est un usage assez médiocre d’options mais même cela présente des avantages. Avec null, tout peut être null, vous devez donc vérifier en permanence, des appels facultatifs indiquant que cette variable est susceptible de manquer, assurez-vous de vérifier
Richard Tingle

Réponses:

109

Le style 2 ne va pas assez en Java 8 pour voir le plein bénéfice. Vous ne voulez pas du if ... usetout. Voir les exemples Oracle . En suivant leurs conseils, nous obtenons:

Style 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Ou un extrait plus long

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
la source
19
En effet, nous interdisons la plupart des utilisations de isPresent (détecté par l'examen du code)
jk.
10
@jk. en effet, s'il y avait une surcharge, void ifPresent(Consumer<T>, Runnable)je plaiderais pour une interdiction totale de isPresentetget
Caleth
10
@ Caleth: Vous pouvez être intéressé par Java 9 ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin
9
Je trouve un inconvénient dans ce style java 8 par rapport au java 6: il trompe les outils de couverture de code. Avec if, cela indique quand vous avez manqué une branche, pas ici.
Thierry
14
@Aaron "réduit considérablement la lisibilité du code". Quelle partie réduit exactement la lisibilité? Cela ressemble plus à un "je l'ai toujours fait différemment et je ne veux pas changer mes habitudes" qu'à un raisonnement objectif.
Voo le
47

Si vous utilisez Optionalune couche de "compatibilité" entre une ancienne API qui peut toujours être renvoyée null, il peut être utile de créer l'option Facultatif (non vide) à la dernière étape, afin de vous assurer que vous disposez de quelque chose. Par exemple, où vous avez écrit:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

J'opterais pour:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Le point est que vous savez qu'il ya un employé non nul de service , de sorte que vous pouvez envelopper que dans un Optionalavec Optional.of(). Ensuite, lorsque vous appelez getEmployee()à ce sujet, vous pouvez ou non engager un employé. Cet employé peut (ou peut-être pas) avoir un identifiant. Ensuite, si vous vous retrouvez avec un identifiant, vous souhaitez l’imprimer.

Il n'y a pas besoin de vérifier explicitement toute null, présence, etc., dans ce code.

Joshua Taylor
la source
4
Quel est l'avantage d'utiliser (...).ifPresent(aMethod)dessus if((...).isPresent()) { aMethod(...); }? Il semble que ce soit simplement une syntaxe supplémentaire pour faire presque la même opération.
Ruslan
2
@Ruslan Pourquoi utiliser une syntaxe spéciale pour un appel spécifique alors que vous avez déjà une solution générale qui fonctionne aussi bien dans tous les cas? Nous appliquons simplement la même idée de map and co ici aussi.
Voo le
1
@Ruslan ... une API renvoie des valeurs NULL au lieu de collections ou de tableaux vides. Par exemple, vous pouvez faire quelque chose comme (en supposant une classe Parent pour laquelle getChildren () retourne un ensemble d'enfants ou null s'il n'y en a pas): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Maintenant, je peux traiter childrenuniformément, par exemple children.forEach(relative::sendGift);. Ceux -ci pourraient même être combinés: Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Tout le passe-partout de la vérification à zéro, etc., ...
Joshua Taylor
1
@Ruslan ... est délégué au code essayé et vrai de la bibliothèque standard. Cela réduit la quantité de code que j'ai, en tant que programmeur, à écrire, tester, déboguer, etc.
Joshua Taylor
1
Content que j'utilise Swift. si let employee = employeeService.employee {print (employee.Id); }
gnasher729
23

Il n’ya pratiquement aucune valeur ajoutée à avoir une Optionalvaleur unique. Comme vous l'avez vu, cela ne fait que remplacer un contrôle nullpar un contrôle de présence.

Il est énorme valeur ajoutée à avoir une Collectionde ces choses. Toutes les méthodes de diffusion en continu introduites pour remplacer les anciennes boucles de bas niveau comprennent les Optionalchoses et font ce qu’il faut faire à leur sujet (ne pas les traiter ou, au final, renvoyer un autre non défini Optional). Si vous traitez n articles plus souvent qu'avec des articles individuels (et que 99% de tous les programmes réalistes le font), le fait de prendre en charge de manière optionnelle les éléments présents constitue un énorme progrès. Dans le cas idéal, vous ne devez jamais vérifier null ni être présent.

Kilian Foth
la source
24
En plus de collections / cours d' eau, une option est également idéal pour faire des API plus autodocumenté, par exemple Employee getEmployee()vs Optional<Employee> getEmployee(). Techniquement, les deux pourraient revenir null, mais l’un d’eux risque beaucoup plus de causer des ennuis.
amon
16
Il y a beaucoup de valeur dans une option d'une valeur unique. Etre capable de ne .map()pas avoir à vérifier null tout au long du chemin est génial. P. ex Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor
9
Je suis en désaccord avec votre commentaire initial sans valeur ajoutée. Facultatif fournit un contexte à votre code. Un rapide coup d'œil peut vous dire de corriger la correction d'une fonction et tous les cas sont couverts. Considérant que, avec la vérification nulle, devriez-vous vérifier chaque type de référence sur chaque méthode? Un cas similaire peut être appliqué aux classes et aux modificateurs public / privé; ils ajoutent une valeur nulle à votre code, non?
ArTs
3
@ JoshuaTaylor Honnêtement, comparé à cette expression, je préfère le chèque à l'ancienne null.
Kilian Foth
2
Un autre avantage important de Optional même avec des valeurs uniques est que vous ne pouvez pas oublier de vérifier la valeur null lorsque vous devez le faire. Sinon, il est très facile d'oublier le chèque et de se retrouver avec un NullPointerException ...
Sean Burton le
17

Tant que vous utilisez Optionalcomme une API sophistiquée isNotNull(), oui, vous ne trouverez aucune différence avec la simple vérification null.

Choses que vous ne devriez PAS utiliser Optionalpour

En utilisant Optionaljuste pour vérifier la présence d'une valeur est mauvais code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Les choses que vous devriez utiliser Optionalpour

Evitez qu'une valeur ne soit pas présente, en en produisant une lorsque cela est nécessaire lors de l'utilisation de méthodes d'API à renvoi nul:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Ou, si créer un nouvel employé coûte trop cher:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

De plus, sachez à tout le monde que vos méthodes risquent de ne pas renvoyer de valeur (si, pour une raison quelconque, vous ne pouvez pas renvoyer une valeur par défaut comme ci-dessus):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

Et enfin, il y a toutes les méthodes de type flux qui ont déjà été expliquées dans d'autres réponses, même si ce ne sont pas vraiment vous qui décidez d'utiliser, Optionalmais vous utilisez les informations Optionalfournies par quelqu'un d'autre.

Walen
la source
1
@Kapep En effet. Nous avons eu ce débat sur / r / java et j'ai dit : «Je vois Optionalune occasion manquée. "Ici, j'ai cette nouvelle Optionalchose que j'ai faite donc vous êtes obligé de vérifier les valeurs nulles. Oh, et au fait ... j'ai ajouté une get()méthode pour que vous n'ayez pas à vérifier quoi que ce soit;);)" . (...) Optionalest agréable comme un signe au néon "CECI POURRAIT ÊTRE NUL", mais la simple existence de sa get()méthode le paralyse " . Mais OP demandait des raisons d'utilisation Optional, pas des raisons de s'y opposer ni des défauts de conception.
Walen
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());Dans votre troisième extrait, le type ne devrait-il pas être Optional<Employee>?
Charlie Harding
2
@immibis De quelle manière estropié? La principale raison à utiliser getest si vous devez interagir avec un code hérité. Je ne peux pas penser à de nombreuses raisons valables d'utilisation du nouveau code get. Cela dit, quand on interagit avec le code existant, il n’ya souvent aucune alternative, mais il n’en reste pas moins que les getdébutants sont capables d’écrire du mauvais code.
Voo le
1
@immibis Je ne l'ai pas mentionné Mapune seule fois et je ne suis au courant de personne ayant effectué un changement aussi radicalement non compatible avec les versions antérieures , alors assurez-vous que vous pouvez intentionnellement concevoir de mauvaises API avec Optional- ne savez pas ce que cela prouve. Un Optionalserait logique pour une tryGetméthode cependant.
Voo le
2
@Voo Mapest un exemple connu de tous. Bien sûr, ils ne peuvent pas faire en sorte que Map.getReturn soit facultatif en raison de la compatibilité ascendante, mais vous pouvez facilement imaginer une autre classe semblable à Map qui ne serait pas soumise à de telles contraintes de compatibilité. Dis class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Maintenant, vous voulez obtenir les détails de l'utilisateur connecté et vous savez qu'ils existent parce qu'ils ont réussi à se connecter et que votre système ne supprime jamais les utilisateurs. Alors vous faites theUserRepository.getUserDetails(loggedInUserID).get().
user253751
12

Le point essentiel pour moi lors de l'utilisation de Optional est de rester clair lorsque le développeur doit vérifier si la fonction renvoie une valeur ou non.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Rodrigo Menezes
la source
8
Cela me déconcerte que tous les trucs fonctionnels astucieux avec les options, bien que très agréables à avoir, sont considérés comme plus importants que ce point ici. Avant Java 8, vous deviez explorer Javadoc pour savoir si vous deviez vérifier la réponse à un appel. Après Java 8, vous savez à partir du type de retour si vous pouvez obtenir "rien" en retour ou non.
Buhb
3
Eh bien, il y a le problème de "l'ancien code" où les choses pourraient toujours retourner à null ... mais oui, pouvoir dire à partir de la signature est génial.
Haakon Løtveit le
5
Oui, cela fonctionne certainement mieux dans les langues où l'option <T> est le seul moyen d'exprimer le fait qu'une valeur peut être manquante, c'est-à-dire les langues sans ptr nul.
dureuill
8

Votre erreur principale est que vous envisagez toujours de manière plus procédurale. Ceci n'est pas conçu comme une critique de vous en tant que personne, c'est simplement une observation. Penser en termes plus fonctionnels va de pair avec le temps et la pratique. Par conséquent, les méthodes sont actuelles et donnent l’impression que les choses correctes les plus évidentes s’appellent à vous. Votre erreur mineure secondaire est la création de votre option dans votre méthode. Facultatif est conçu pour aider à documenter le fait que quelque chose peut ou non renvoyer une valeur. Vous pourriez ne rien obtenir.

Cela vous a conduit à écrire un code parfaitement lisible qui semble parfaitement raisonnable, mais vous avez été séduit par les viles tentacules jumelles qui se font et sont présentes.

Bien sûr, la question devient rapidement "pourquoi sont-ils présents et même y arriver?"

Une chose qui manque à beaucoup de gens ici, c'est que isPresent () est que ce n'est pas quelque chose qui est fait pour le nouveau code écrit par des gens pleinement conscients de l'utile dieu que sont les lambda, et qui aime le fonctionnel.

Cependant, il nous donne quelques (deux) bons, grands avantages glamour (?):

  • Il facilite la transition du code hérité pour utiliser les nouvelles fonctionnalités.
  • Il facilite les courbes d'apprentissage de facultatif.

Le premier est plutôt simple.

Imaginez que vous ayez une API qui ressemble à ceci:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

Et vous aviez une classe héritée utilisant ceci en tant que tel (complétez les blancs):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Un exemple artificiel pour être sûr. Mais supporte avec moi ici.

Java 8 est maintenant lancé et nous nous efforçons de monter à bord. L’une des choses que nous faisons est donc de remplacer notre ancienne interface par quelque chose qui retourne facultatif. Pourquoi? Parce que, comme quelqu'un d'autre l'a déjà gracieusement mentionné: Cela élimine la question de savoir si quelque chose peut être nul ou non. Cela a déjà été souligné par d'autres. Mais maintenant nous avons un problème. Imaginez que nous ayons (excusez-moi pendant que je frappe alt + F7 une méthode innocente), 46 endroits où cette méthode est appelée dans un code hérité bien testé qui fait un excellent travail sinon. Maintenant, vous devez mettre à jour tous ces éléments.

C’est là que brille isPresent.

Parce que maintenant: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {lance la nouvelle NoSuchSnickersException (); } else {consumer.giveDiabetes (lastSnickers); }

devient:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

Et ceci est un changement simple que vous pouvez donner au nouveau junior: il peut faire quelque chose d’utile, et il va pouvoir explorer le code en même temps. gagnant-gagnant. Après tout, quelque chose qui ressemble à ce modèle est assez répandu. Et maintenant, vous n'avez pas à réécrire le code pour utiliser des lambdas ou quoi que ce soit. (Dans ce cas particulier, ce serait trivial, mais je laisse penser au lecteur des exemples où ce serait difficile comme exercice.)

Notez que cela signifie que votre façon de faire est essentiellement une façon de traiter le code hérité sans faire de réécriture coûteuse. Alors, qu'en est-il du nouveau code?

Eh bien, dans votre cas, où vous voulez simplement imprimer quelque chose, vous feriez simplement:

snickersCounter.lastConsumedSnickers (). ifPresent (System.out :: println);

Ce qui est assez simple et parfaitement clair. Le point qui remonte lentement à la surface est qu’il existe des cas d’utilisation pour get () et isPresent (). Ils sont là pour vous permettre de modifier mécaniquement le code existant pour utiliser les nouveaux types sans trop y penser. Ce que vous faites est donc malavisé des manières suivantes:

  • Vous appelez une méthode qui peut renvoyer null. L'idée correcte serait que la méthode retourne null.
  • Vous utilisez les méthodes héritées du pansement pour traiter cette option, au lieu d'utiliser les nouvelles méthodes savoureuses qui contiennent la fantaisie lambda.

Si vous souhaitez utiliser facultatif comme simple contrôle de sécurité null, vous devez simplement procéder comme suit:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Bien sûr, la belle version de ceci ressemble à ceci:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

En passant, bien que cela ne soit absolument pas nécessaire, je recommande d’utiliser une nouvelle ligne par opération, afin de faciliter la lecture. Facile à lire et à comprendre bat la concision tous les jours de la semaine.

Ceci est bien sûr un exemple très simple où il est facile de comprendre tout ce que nous essayons de faire. Ce n'est pas toujours aussi simple dans la vie réelle. Mais remarquez comment dans cet exemple, ce que nous exprimons sont nos intentions. Nous voulons OBTENIR l'employé, OBTENIR son identifiant et, si possible, l'imprimer. C'est la deuxième grande victoire avec Optional. Cela nous permet de créer un code plus clair. Je pense aussi que faire des choses comme créer une méthode qui fait beaucoup de choses pour pouvoir l’alimenter sur une carte est en général une bonne idée.

Haakon Løtveit
la source
1
Le problème ici est que vous essayez de donner l'impression que ces versions fonctionnelles sont aussi lisibles que les versions précédentes, dans un cas, même en disant qu'un exemple était "parfaitement clair". Ce n'est pas le cas. Cet exemple était clair, mais pas tout à fait, car il nécessite plus de réflexion. map(x).ifPresent(f)n'a tout simplement pas le même sens intuitif que le if(o != null) f(x)fait. La ifversion est parfaitement claire. C’est un autre cas de personnes qui sautent dans un groupe populaire, et non de véritables progrès.
Aaron
1
Notez que je ne dis pas que l’utilisation de la programmation fonctionnelle ou de Optional. Je faisais référence uniquement à l'utilisation de facultatif dans ce cas précis, et plus encore à la lisibilité, puisque plusieurs personnes agissent comme si ce format était tout aussi lisible, voire plus, que if.
Aaron
1
Cela dépend de nos notions de clarté. Bien que vous présentiez de très bons arguments, je voudrais également souligner que pour beaucoup de gens, les lambdas étaient nouveaux et étranges à un moment donné. J'oserais miser sur un cookie Internet que peu de gens disent isPresent et ressentent un soulagement merveilleux: ils peuvent désormais mettre à jour le code hérité, puis apprendre la syntaxe lambda plus tard. D'autre part, les personnes ayant une connaissance linguistique de Lisp, de Haskell ou d'une expérience linguistique similaire étaient probablement ravies de pouvoir maintenant appliquer des modèles familiers à leurs problèmes de
langage
@Aaron - La clarté n'est pas le problème des types facultatifs. Je trouve personnellement qu'ils ne diminuent pas la clarté, et il y a des cas (bien que ce ne soit pas le cas) où ils l'augmentent, mais le principal avantage est que (tant que vous évitez d'utiliser isPresentet getautant que possible, de façon réaliste) améliorer la sécurité . Il est plus difficile d'écrire un code incorrect qui ne permet pas de vérifier l'absence de valeur si le type est Optional<Whatever>plutôt que d'utiliser une valeur nullable Whatever.
Jules
Même si vous retirez immédiatement les valeurs, tant que vous n'utilisez pasget , vous êtes obligé de choisir quoi faire quand une valeur manquante se produit: vous utiliseriez orElse()(ou orElseGet()) pour fournir une valeur par défaut, ou orElseThrow()pour jeter une valeur choisie. exception qui documente la nature du problème , ce qui est mieux qu'un NPE générique qui n'a pas de sens jusqu'à ce que vous creusiez dans la trace de la pile.
Jules
0

Je ne vois pas l'avantage dans le style 2. Vous devez toujours comprendre que vous avez besoin du contrôle nul et qu'il est maintenant encore plus volumineux et donc moins lisible.

Le meilleur style serait, si employeeServive.getEmployee () renverrait Facultatif et le code deviendrait alors:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

De cette façon, vous ne pouvez pas appeleremployeeServive.getEmployee (). GetId () et vous remarquez que vous devez gérer la valeur optionnelle d'une manière ou d'une autre. Et si, dans l'ensemble de votre méthode, les méthodes de la base de code ne renvoient jamais la valeur null (ou presque jamais avec des exceptions bien connues de votre équipe à cette règle), la sécurité contre NPE sera renforcée.

utilisateur470365
la source
12
Facultatif devient une complication inutile au moment où vous utilisez isPresent(). Nous utilisons optionnel pour ne pas avoir à tester.
candied_orange
2
Comme d'autres l'ont dit, ne l'utilisez pas isPresent(). C'est la mauvaise approche pour les optionnels. Utilisez mapou à la flatMapplace.
Andres F.
1
@CandiedOrange Et pourtant, la réponse la plus votée (dire d'utiliser ifPresent) n'est qu'un autre moyen de tester.
user253751
1
@CandiedOrange ifPresent(x)est tout aussi un test que if(present) {x}. La valeur de retour n'est pas pertinente.
user253751
1
@CandiedOrange Néanmoins, vous avez initialement dit "afin que nous n'ayons pas à tester." Vous ne pouvez pas dire "afin que nous n'ayons pas à tester" alors que vous testez encore ... ce que vous êtes.
Aaron
0

Je pense que le plus grand avantage est que si votre signature de la méthode retourne un Employeevous ne cochez NULL. Vous savez par cette signature qu'il est garanti de renvoyer un employé. Vous n'avez pas besoin de gérer un cas d'échec. Avec une option, vous savez que vous faites.

Dans le code que j'ai vu, certaines vérifications nulles n'échouent jamais car les utilisateurs ne veulent pas tracer dans le code pour déterminer si une valeur null est possible, ils lancent donc des vérifications nulles partout de manière défensive. Cela rend le code un peu plus lent, mais plus important encore, il le rend beaucoup plus bruyant.

Cependant, pour que cela fonctionne, vous devez appliquer ce motif de manière cohérente.

Traîneau
la source
1
Le moyen le plus simple pour y parvenir consiste à utiliser @Nullableet @ReturnValuesAreNonnullByDefaultà l’analyse statique.
Maaartinus
@maaartinus Ajouter des outils supplémentaires sous forme d'analyse statique et de documentation dans les annotations du décorateur est-il plus simple que de prendre en charge les API à la compilation?
Traîneau
L'ajout d'outillage supplémentaire est en quelque sorte plus simple, car il ne nécessite aucune modification de code. De plus, vous voulez en avoir de toute façon car cela corrige d’autres bogues. +++ J'ai écrit un script trivial ajoutant package-info.javaavec @ReturnValuesAreNonnullByDefaulttous mes paquets, donc tout ce que je dois faire est d'ajouter @Nullableoù vous devez passer à Optional. Cela signifie moins de caractères, pas de déchets ajoutés et pas de temps d’exécution supplémentaire. Je n'ai pas besoin de consulter la documentation telle qu'elle apparaît lorsque vous survolez la méthode. L'outil d'analyse statique détecte les erreurs et les modifications ultérieures de nullabilité.
Maaartinus
Ma plus grande préoccupation Optionalest que cela ajoute une autre façon de traiter la nullité. Si c'était en Java depuis le tout début, tout irait bien, mais maintenant, c'est très pénible. Aller à la manière de Kotlin serait beaucoup mieux à mon humble avis. +++ Notez qu'il List<@Nullable String>s'agit toujours d'une liste de chaînes, alors que ce List<Optional<String>>n'est pas le cas.
Maaartinus
0

Ma réponse est: ne le fais pas. Au moins réfléchir à deux fois si c'est vraiment une amélioration.

Une expression (tirée d'une autre réponse) comme

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

semble être la bonne façon fonctionnelle de traiter les méthodes de retour initialement nullables. Ça a l'air sympa, ça ne jette jamais et ça ne vous fait jamais penser à gérer le cas "absent".

Il a l'air mieux que la façon à l'ancienne

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

ce qui montre qu'il peut y avoir des elseclauses.

Le style fonctionnel

  • semble complètement différent (et est illisible pour les personnes qui ne l'utilisent pas)
  • est une douleur à déboguer
  • ne peut pas être facilement étendu pour traiter le cas "absent" ( orElsen'est pas toujours suffisant)
  • induit en erreur en ignorant le cas "absent"

En aucun cas, il ne devrait être utilisé comme une panacée. S'il était universellement applicable, alors la sémantique de l' .opérateur devrait être remplacée par la sémantique de l' ?.opérateur , le NPE oublié et tous les problèmes ignorés.


Tout en étant un grand fan de Java, je dois dire que le style fonctionnel n'est qu'un substitut du pauvre homme Java à la déclaration

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

bien connu d'autres langues. Sommes-nous d'accord pour dire que cette déclaration

  • n'est pas (vraiment) fonctionnel?
  • est très similaire au code de style ancien, juste beaucoup plus simple?
  • est beaucoup plus lisible que le style fonctionnel?
  • est-ce que le débogueur est convivial?
maaartinus
la source
Vous semblez décrire de C # mise en œuvre de ce concept avec une fonctionnalité de langage comme tout à fait différente de Java mise en œuvre avec une classe de bibliothèque de base. Ils ont le même aspect pour moi
Caleth
@ Caleth Pas moyen. Nous pourrions parler du "concept de simplification d’une évaluation null-safe", mais je n’appellerais pas cela un "concept". Nous pouvons dire que Java implémente la même chose en utilisant une classe de bibliothèque principale, mais c'est juste un gâchis. Commencez simplement par le employeeService.getEmployee().getId().apply(id => System.out.println(id));rendre null-safe une fois à l’aide de l’opérateur de navigation Safe et une fois à l’aide de Optional. Bien sûr, nous pourrions appeler cela un "détail de mise en oeuvre", mais la mise en oeuvre compte. Il y a des cas où les lamdas rendent le code plus simple et plus agréable, mais ici, c'est juste un mauvais abus.
Maaartinus
Bibliothèque de classes: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);fonction de la langue: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Deux syntaxes, mais elles finissent par ressembler beaucoup à moi. Et le "concept" est Optionalaka NullableakaMaybe
Caleth
Je ne comprends pas pourquoi vous pensez que l’un d’entre eux est un "mauvais traitement" et que l’autre est bon. Je pourrais comprendre que vous pensiez à la fois "laide abus" ou les deux bien.
Caleth
@ Caleth Une différence est qu'au lieu d'ajouter deux points d'interrogation, vous devez tout réécrire. Le problème n'est pas que la fonction Langue et les codes de classe de la bibliothèque diffèrent beaucoup. C'est bon. Le problème est que le code original et les codes de classe Library diffèrent beaucoup. Même idée, code différent. C'est dommage. +++Ce qui est maltraité, ce sont les lamdas qui gèrent la nullité. Les Lamdas vont bien, mais Optionalne le sont pas. La nullabilité nécessite simplement un support linguistique approprié, comme dans Kotlin. Un autre problème: List<Optional<String>>est sans rapport avec List<String>, nous aurions besoin qu’ils soient liés.
Maaartinus
0

Facultatif est un concept (un type d'ordre supérieur) issu de la programmation fonctionnelle. Son utilisation supprime la vérification de nullité imbriquée et vous sauve de la pyramide du destin.

Petras Purlys
la source