web-dev-qa-db-fra.com

Obtenez de la valeur d'une option ou d'une autre

J'ai deux instances Java.util.Optional Et je veux obtenir un Optional qui soit:

  • A la valeur du premier Facultatif, s'il a une valeur.
  • A la valeur du second Facultatif, s'il a une valeur.
  • Est vide ni l'un ni l'autre Facultatif a une valeur.

Existe-t-il un moyen simple de le faire, c'est-à-dire qu'il existe déjà une API pour le faire?

Les expressions suivantes feront cela, mais je dois mentionner deux fois la première option:

firstOptional.isPresent() ? firstOptional : secondOptional

C'est exactement ce que fait com.google.common.base.Optional.or(), mais cette méthode n'est pas présente dans l'API de Java 8).


La réponse acceptée par aioobe énumère quelques approches alternatives pour surmonter cette omission de l'API Optional là où une telle valeur doit être calculée (ce qui répond à ma question). J'ai maintenant choisi d'ajouter une fonction utilitaire à ma base de code:

public static <T> Optional<T> or(Optional<T> a, Optional<T> b) {
    if (a.isPresent())
        return a;
    else
        return b;
}
45
Feuermurmel

Mise à jour: À partir de Java 9 ( changeset 12885 ) vous pouvez faire

firstOptional.or(() -> secondOptional);

Pour Java 8, lisez la suite ...

Si vous voulez éviter de mentionner firstOptional deux fois, vous devrez probablement choisir quelque chose comme

firstOptional.map(Optional::of).orElse(secondOptional);

ou

Optional.ofNullable(firstOptional.orElse(secondOptional.orElse(null)));

Mais la variante la plus lisible est probablement de simplement faire

Optional<...> opt = firstOptional.isPresent()  ? firstOptional
                  : secondOptional.isPresent() ? secondOptional
                  : Optional.empty();

Si quelqu'un tombe sur cette question mais a une liste d'options, je suggérerais quelque chose comme

Optional<...> opt = optionals.stream()
                             .filter(Optional::isPresent)
                             .findFirst()
                             .orElse(Optional.empty());
57
aioobe

EDIT: Je pensais totalement que vous utilisiez le Optional de Guava à l'origine. J'ai mis à jour ma réponse pour fournir à la fois Guava et Java 8 syntaxe pour leurs classes Optional respectives.

Java 8 en option

Vous pouvez le raccourcir jusqu'à ceci:

firstOptional.orElse(secondOptional.orElse(EMPTY_VALUE))

Je ne sais pas ce que vous vouliez dire dans votre troisième puce par "vide". Si vous vouliez dire null, cela fera l'affaire:

firstOptional.orElse(secondOptional.orElse(null))

orElse() est une méthode sur Optional qui retournera la valeur si elle est présente, sinon elle renverra la valeur que vous avez fournie comme argument à orElse().

Goyave en option

Vous pouvez le raccourcir jusqu'à ceci:

firstOptional.or(secondOptional.or(EMPTY_VALUE))

Je ne sais pas ce que vous vouliez dire dans votre troisième puce par "vide". Si vous vouliez dire null, cela fera l'affaire:

firstOptional.or(secondOptional.orNull())

or() est une méthode sur Optional qui retournera la valeur si elle est présente, sinon elle renverra la valeur que vous avez fournie comme argument à or().

8
Erik Gillespie

J'ai rencontré quelques fois un problème qui aurait pu être résolu avec JDK 9 Optional::or et n'a pas pu parce que nous utilisons JDK 8. Enfin, j'ai ajouté une classe util avec cette méthode:

@SafeVarargs
public static <T> Optional<T> firstPresent(final Supplier<Optional<T>>... optionals) {
    return Stream.of(optionals)
            .map(Supplier::get)
            .filter(Optional::isPresent)
            .findFirst()
            .orElse(Optional.empty());
}

Vous pouvez maintenant fournir n'importe quel nombre d'options à cette méthode et elles seront évaluées paresseusement comme suit:

    final Optional<String> username = OptionalUtil.firstPresent(
            () -> findNameInUserData(user.getBasicData()),
            () -> findNameInUserAddress(user.getAddress()),
            () -> findDefaultUsername());

Maintenant, findNameInUserAddress ne sera appelé que si findNameInUserData retourne vide. findDefaultUsername ne sera appelé que si findNameInUserData et findNameInUserAddress renvoient vide etc.

7
jahmaican