web-dev-qa-db-fra.com

Facultatif ouElse Facultatif dans Java

Je travaille avec le nouveau type facultatif dans Java 8 , et je suis tombé sur une opération qui semble être une opération courante qui n'est pas prise en charge de manière fonctionnelle: un "ou ElseOptional"

Considérez le modèle suivant:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

Il existe de nombreuses formes de ce modèle, mais cela revient simplement à vouloir un "orElse" sur une option qui prend une fonction produisant une nouvelle option, appelée uniquement si celle-ci n'existe pas.

Sa mise en œuvre ressemblerait à ceci:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

Je suis curieux de savoir s'il existe une raison pour laquelle une telle méthode n'existe pas, si j'utilise tout simplement Facultatif de manière non intentionnelle, et quelles autres manières les gens ont imaginées pour traiter ce cas.

Je devrais dire que je pense que les solutions impliquant des classes/méthodes d'utilitaires personnalisés ne sont pas élégantes, car les personnes travaillant avec mon code ne sauront pas nécessairement qu'elles existent.

En outre, si quelqu'un sait, une telle méthode sera-t-elle incluse dans la JDK 9 et où pourrais-je proposer une telle méthode? Cela me semble être une omission assez flagrante de l’API.

125
Yona Appletree

Cela fait partie de JDK 9 sous la forme de or, qui prend un Supplier<Optional<T>>. Votre exemple serait alors:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

Pour plus de détails, voir the Javadoc ou ce post j'ai écrit.

75
Nicolai

L’approche "try services" la plus propre compte tenu de l’API actuelle serait:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

L'important n'est pas la chaîne d'opérations (constante) que vous devez écrire une fois, mais la facilité avec laquelle vous pouvez ajouter un autre service (ou modifier la liste des services en général). Ici, ajouter ou supprimer un seul ()->serviceX(args) suffit.

En raison de l'évaluation paresseuse des flux, aucun service ne sera appelé si un service précédent renvoyait un Optional non vide.

59
Holger

Ce n'est pas joli, mais ça va marcher:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup) est un modèle assez pratique à utiliser avec Optional. Cela signifie "Si cette Optional contient la valeur v, donnez-moi func(v), sinon donnez-moi sup.get()".

Dans ce cas, nous appelons serviceA(args) et obtenons un Optional<Result>. Si cette Optional contient la valeur v, nous voulons obtenir Optional.of(v), mais si elle est vide, nous voulons obtenir serviceB(args). Rinçage-répéter avec plus de choix.

Les autres utilisations de ce modèle sont

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)
30
Misha

C’est peut-être ce que vous recherchez: Obtenir la valeur d’un optionnel ou d’un autre

Sinon, vous voudrez peut-être jeter un oeil à Optional.orElseGet . Voici un exemple de ce que je pense :

_result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));
_
26
aioobe

En supposant que vous soyez toujours sur JDK8, il existe plusieurs options.

Option n ° 1: créez votre propre méthode d'assistance

Par exemple.:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

Pour que vous puissiez faire:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

Option n ° 2: utiliser une bibliothèque

Par exemple. google guava's Optional prend en charge une opération or() appropriée (comme JDK9), par exemple:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(Où chacun des services retourne com.google.common.base.Optional, plutôt que Java.util.Optional).

4
Sheepy

Cela ressemble à un bon ajustement pour le filtrage de motif et à une interface Option plus traditionnelle avec des implémentations Some et None (telles que celles de Javaslang , FunctionalJava ) ou un fainéant Peut-être implémentation dans cyclops-react . Je suis l'auteur de cette bibliothèque.

Avec cyclops-react , vous pouvez également utiliser des structures correspondance de motif sur les types JDK. Pour Facultatif, vous pouvez apparier les cas présents et absents via le motif visiteur . ça ressemblerait à quelque chose comme ça -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
2
John McClean