web-dev-qa-db-fra.com

Pourquoi utiliser Facultatif dans Java 8+ au lieu des vérifications de pointeur nul traditionnelles?

Nous avons récemment migré vers Java 8. Maintenant, je vois des applications inondées d'objets Optional.

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 de Optional<Employee> employeeOptional = employeeService.getEmployee(); lorsque le service lui-même renvoie facultatif:

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

Consolidé à partir de la compréhension de toutes les réponses et des recherches approfondies sur blog

118
user3198603

Le style 2 ne fonctionne pas Java 8 assez pour voir tous les avantages. Vous ne voulez pas que le if ... use du tout. Voir exemples d'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));
116
Caleth

Si vous utilisez Optional comme couche de "compatibilité" entre une ancienne API qui peut toujours renvoyer null, il peut être utile de créer l'option (non vide) au dernier stade qui vous êtes sûr que vous avez 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 fait est que vous sachez qu'il y a un employé non nul service, vous pouvez donc envelopper cela dans un Optional avec Optional.of(). Ensuite, lorsque vous appelez getEmployee() à ce sujet, vous pouvez ou non obtenir un employé. Cet employé peut (ou peut-être pas) avoir une pièce d'identité. Ensuite, si vous vous retrouvez avec une pièce d'identité, vous souhaitez l'imprimer.

Il n'est pas nécessaire de vérifier explicitement tout null, présence, etc., dans ce code.

48
Joshua Taylor

Il n'y a pratiquement aucune valeur ajoutée à avoir un Optional d'une seule valeur. Comme vous l'avez vu, cela remplace simplement une vérification de null par une vérification de présence.

Il y a énorme valeur ajoutée à avoir un Collection de telles choses. Toutes les méthodes de streaming introduites pour remplacer les anciennes boucles de bas niveau comprennent les choses Optional et font ce qu'il faut faire (ne pas les traiter ou finalement renvoyer un autre Optional non défini). Si vous traitez avec n éléments plus souvent qu'avec des éléments individuels (et 99% de tous les programmes réalistes le font), alors c'est une énorme amélioration d'avoir construit -en support pour éventuellement présenter des choses. Dans le cas idéal, vous n'avez jamais à vérifier la présence de null ou .

23
Kilian Foth

Tant que vous utilisez Optional comme une API sophistiquée pour isNotNull(), alors oui, vous ne trouverez aucune différence en vérifiant simplement null.

Choses à ne PAS utiliser Optional pour

Utiliser Optional juste pour vérifier la présence d'une valeur est Bad 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 devez utiliser Optional pour

Évitez qu'une valeur ne soit présente, en en produisant une si nécessaire lorsque vous utilisez des méthodes d'API à retour nul:

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

Ou, si la création d'un nouvel employé est trop coûteuse:

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

De plus, en informant tout le monde que vos méthodes peuvent ne pas retourner de valeur (si, pour une raison quelconque, vous ne pouvez pas retourner 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, bien qu'elles ne soient pas vraiment vous décidant d'utiliser Optional mais faisant usage de Optional fourni par quelqu'un d'autre.

18
walen

Le point clé pour moi en utilisant Facultatif est de rester clair lorsque le développeur doit vérifier si la fonction retourne la 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());
12
Rodrigo Menezes

Votre principale erreur est que vous pensez toujours en termes plus procéduraux. Ce n'est pas une critique de vous en tant que personne, c'est simplement une observation. Penser en termes plus fonctionnels vient avec du temps et de la pratique, et donc les méthodes sont présentes et ressemblent aux choses correctes les plus évidentes à appeler pour vous. Votre erreur mineure secondaire crée votre facultatif dans votre méthode. Le Facultatif est destiné à aider à documenter que quelque chose peut ou non retourner une valeur. Vous pourriez ne rien obtenir.

Cela vous a amené à écrire du code parfaitement lisible qui semble parfaitement raisonnable, mais vous avez été séduit par les viles tentatrices jumelles qui sont et sont présentes.

Bien sûr, la question devient rapidement "pourquoi est présent et y arrive-t-il?"

Une chose qui manque à beaucoup de gens ici, c'est que isPresent () est que ce n'est pas quelque chose qui est fait pour du nouveau code écrit par des personnes entièrement à bord avec à quel point les lambdas utiles sont incroyables et qui aiment la chose fonctionnelle.

Cependant, cela nous donne quelques (deux) bons, excellents avantages glamour (?):

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

Le premier est assez 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 (remplissez les blancs):

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

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

Java 8 est maintenant lancé et nous nous efforçons de monter à bord. Donc, l'une des choses que nous faisons est que nous voulons 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 supprime la conjecture de savoir si quelque chose peut être nul 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 sur 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 autrement. Vous devez maintenant mettre à jour tous ces éléments.

C'est là que brille isPresent.

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

devient:

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

Et c'est un changement simple que vous pouvez donner au nouveau junior: il peut faire quelque chose d'utile et il pourra explorer la base de 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 plus à réécrire le code pour utiliser des lambdas ou quoi que ce soit. (Dans ce cas particulier, ce serait trivial, mais je laisse au lecteur des exemples où ce serait difficile comme exercice pour le lecteur.)

Notez que cela signifie que la façon dont vous l'avez fait est essentiellement un moyen de gérer le code hérité sans effectuer de réécritures coûteuses. Et le 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 erroné des manières suivantes:

  • Vous appelez une méthode qui peut retourner null. L'idée correcte serait que la méthode retourne null.
  • Vous utilisez les anciennes méthodes de bandaid pour gérer cette option, au lieu d'utiliser les nouvelles méthodes savoureuses qui contiennent de la fantaisie lambda.

Si vous souhaitez utiliser Facultatif comme simple contrôle de sécurité nulle, ce que vous auriez dû faire est simplement le suivant:

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

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

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

Soit dit en passant, bien que cela ne soit en aucun cas requis, je recommande d'utiliser une nouvelle ligne par opération, afin qu'elle soit plus facile à lire. Facile à lire et à comprendre bat la concision n'importe quel jour 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 vraie vie. Mais remarquez comment dans cet exemple, ce que nous exprimons, ce sont nos intentions. Nous voulons OBTENIR l'employé, OBTENIR son ID et, si possible, l'imprimer. Il s'agit de 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 faire une méthode qui fait un tas de trucs pour que vous puissiez l'alimenter sur une carte est en général une bonne idée.

8
Haakon Løtveit

Ma réponse est: ne le faites tout simplement pas. Réfléchissez au moins à 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 à l'origine nulles. Il a l'air bien, il ne jette jamais et il ne vous fait jamais penser à gérer le cas "absent".

Il a l'air mieux que l'ancienne

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

ce qui rend évident qu'il peut y avoir des clauses else.

Le style fonctionnel

  • semble complètement différent (et illisible pour les personnes qui n'y sont pas habituées)
  • est une douleur à déboguer
  • ne peut pas être facilement étendu pour gérer le cas "absent" (orElse n'est pas toujours suffisant)
  • induit en erreur le fait d'ignorer le cas "absent"

En aucun cas, il ne devrait être utilisé comme panacée. S'il était universellement applicable, alors la sémantique du . L'opérateur doit être remplacé par la sémantique de ?. operator , 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 est juste un pauvre Java homme substitut de la déclaration)

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

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

  • n'est pas (vraiment) fonctionnel?
  • est très similaire à l'ancien code, beaucoup plus simple?
  • est beaucoup plus lisible que le style fonctionnel?
  • est compatible avec le débogueur?
0
maaartinus

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

0
Petras Purlys

Que se passerait-il si "employeeService" est lui-même nul? Dans les deux extraits de code, l'exception de pointeur nul sera déclenchée.

Il peut être manipulé sans utiliser en option comme:

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

Mais comme vous pouvez le voir, il y a plusieurs vérifications if et il peut être étendu à une longue hiérarchie avec une longue structure imbriquée comme employeeService.getEmployee (). GetDepartment (). GetName () .... getXXX (). GetYYY () etc.

Pour gérer ce type de scénarios, nous pouvons utiliser la classe Optional comme:

Optional.ofNullable(employeeService)
     .map(service -> service.getEmployee())
     .map(employee -> employee.getId())
     .ifPresent(System.out::println);

Et il peut gérer n'importe quelle taille d'imbrication.

Pour tout savoir sur Java Facultatif, lisez mon article pour ce sujet: Facultatif dans Java 8

0
Shubham Bansal

Je ne vois aucun avantage dans le style 2. Vous devez toujours réaliser que vous avez besoin de la vérification nulle et maintenant c'est encore plus grand, donc moins lisible.

Un meilleur style serait, si employeeServive.getEmployee () retournait Facultatif et que le code deviendrait:

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 facultative d'une manière ou d'une autre. Et si dans l'ensemble de votre code, les méthodes ne renvoient jamais null (ou presque jamais avec des exceptions bien connues de votre équipe à cette règle), cela augmentera la sécurité contre NPE.

0
user470365

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

Dans le code, j'ai vu qu'il y a des vérifications nulles qui n'échoueront jamais car les gens ne veulent pas suivre le code pour déterminer si une nullité est possible, alors ils lancent des vérifications nulles partout de manière défensive. Cela rend le code un peu plus lent, mais plus important encore, il rend le code beaucoup plus bruyant.

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

0
Sled