web-dev-qa-db-fra.com

Comment retourner quand un optionnel est vide?

J'aime que les options se trouvent maintenant dans la bibliothèque standard Java. Mais il y a un problème fondamental que je ne cesse de rencontrer et que je n'ai pas trouvé comment résoudre le mieux (le plus facile à lire et à comprendre, le plus joli, le plus court): 

Comment revenir d'une méthode quand un optionnel est vide?

Je recherche une solution générale qui fonctionne pour différentes combinaisons de nombres d’options et de tailles de blocs de code.

Dans les exemples suivants, je vais essayer de montrer ce que je veux dire:

void m1() {
    // When I get an optional:
    Optional<String> o = getOptional();

    // And want to return if it's empty
    if (!o.isPresent()) return;

    // In the whole rest of the method I have to call Optional.get 
    // every time I want the value:
    System.out.println(o.get());

    // Which is pretty ugly and verbose!
}


void m2() {
    // If I instead return null if a value is absent:
    String s = getNullabe();
    if (s == null) return;

    // Then I can use the value directly:
    System.out.println(s);
}

Cette question concerne comment obtenir le bon aspect des deux exemples ci-dessus: le type safe de l’option et la brièveté des types nullable.

La suite des exemples l'illustre davantage.

void m3() {
    // If I on the other hand want to throw on empty that's pretty and compact:
    String s = getOptional()
        .orElseThrow(IllegalStateException::new);

    System.out.println(s);
}

void m4() {
    Optional<String> o = getOptional();
    if (!o.isPresent()) return;

    // I can of course declare a new variable for the un-optionalised string:
    String s = o.get();

    System.out.println(s);

    // But the old variable still remains in scope for the whole method 
    // which is ugly and annoying.
    System.out.println(o.get());
}


void m5() {
    // This is compact and maybe pretty in some ways:
    getOptional().ifPresent(s -> {
        System.out.println(s);

        // But the extra level of nesting is annoying and it feels 
        // wrong to write all the code in a big lambda.

        getOtherOptional().ifPresent(i -> {
            // Also, more optional values makes it really weird and 
            // pretty hard to read,  while with nullables I would 
            // get no extra nesting, it would looks good and be 
            // easy to read.
            System.out.println("i: " + i);

            // It doesn't work in all cases either way.
        });
    });
}


Optional<String> getOptional() {
    throw new UnsupportedOperationException();
}

Optional<Integer> getOtherOptional() {
    throw new UnsupportedOperationException();
}

String getNullabe() {
    throw new UnsupportedOperationException();
}

Comment puis-je retourner d'une méthode si un facultatif est vide, sans avoir à utiliser get dans le reste de la méthode, sans déclarer une variable supplémentaire et sans niveaux supplémentaires d'imbrication de blocs?

Ou si ce n'est pas possible, quel est le meilleur moyen de gérer cette situation?

16
Lii

Vous pouvez utiliser orElse(null):

String o = getOptional().orElse(null);
if (o == null) {
    return;
}
19
dnault

Vous pouvez utiliser les méthodes ifPresent et map à la place. Si la fonction est annulée et que vous devez faire des effets secondaires, vous pouvez utiliser ifPresent.

optional.ifPresent(System.out::println); 

Si une autre méthode dépend de la valeur Optional, cette méthode devra peut-être également renvoyer une option et utiliser la méthode map.

Optional<Integer> getLength(){
    Optional<String> hi = Optional.of("hi");
    return hi.map(String::length)
}

La plupart au moment où vous appelez isPresent et get, vous utilisez mal Optional.

8
Sleiman Jneidi

Si vous ne l'utilisez pas, vous ne devez pas créer un nouveau lambda, vous pouvez simplement utiliser une référence de méthode:

getOptional().ifPresent(System.out::println);

Cela ne résout toutefois pas vraiment le cas où vous souhaitez conditionner la présence de deux options. Mais comme alternative à 

// And want to return if it's empty
if (!o.isPresent()) return;

pourquoi ne pas simplement inverser la condition, qui fonctionne bien dans le cas imbriqué, aussi? Il n'est pas nécessaire de rendre le retour explicite:

if (o.isPresent()) {
  System.out.println(o.get());
  if (oo.isPresent()) {
    System.out.println(oo.get());
  }
}

Cependant, ce type de cas d'utilisation suggère que vous ne tirez pas vraiment parti de la valeur facultative par opposition à une valeur nullable. En général, si vous utilisez isPresent et obtenez, il se peut que Optional ne vous rapporte pas vraiment beaucoup (sauf que cela vous oblige à prendre en compte le cas où la valeur est manquante). L'utilisation de ifPresent, map, filter et d'autres méthodes "plus fonctionnelles" pourrait être une utilisation plus typique d'une valeur facultative.


Mais dans tous les cas, veuillez ne pas renvoyer null lorsque vous promettez une option. Bien qu'il soit parfaitement légal de retourner null lorsqu'un objet est attendu, l'objectif de Optional est précisément d'éviter d'avoir à vérifier la valeur null. Alors ne fais pas:

Optional<String> getOptional() {
    return null;
}

mais au lieu de cela:

Optional<String> getOptional() { 
  return Optional.empty();
}

Sinon vous finissez par avoir à faire:

Optional<String> o = getOptional();
if (o != null && o.isPresent()) {
  // ...
}

qui fait vraiment juste le même genre de chose deux fois. Utilisez un facultatif, ou utilisez une valeur nullable, mais ne faites pas les deux!

5
Joshua Taylor

Je ne pense pas que ce que vous demandez soit réellement possible, mais je voudrais suggérer de simplement prendre tout votre code qui fonctionne directement sur votre chaîne et de l’envelopper dans une fonction. Donc, votre fonction devient quelque chose comme ceci:

void m4() {
    Optional<String> o = getOptional();
    if (!o.isPresent()) return;

    doThings(o.get());
}

void doThings(String s){
    System.out.println(s);
    //do whatever else with the string.
}

De cette façon, vous avez uniquement la chaîne dans la portée et vous n'avez pas à appeler .get() à chaque fois que vous souhaitez y accéder.

0
Luka Jacobowitz