web-dev-qa-db-fra.com

Quelle est la manière la plus élégante de combiner des options?

Voici ce que j'ai jusqu'à présent:

Optional<Foo> firstChoice = firstChoice();
Optional<Foo> secondChoice = secondChoice();
return Optional.ofNullable(firstChoice.orElse(secondChoice.orElse(null)));

Cela me semble hideux et inutile. Si firstChoice est présent, je calcule inutilement secondChoice.

Il y a aussi une version plus efficace:

Optional<Foo> firstChoice = firstChoice();
if(firstChoice.isPresent()) {
 return firstChoice;
} else {
 return secondChoice();
}

Ici, je ne peux pas enchaîner une fonction de mappage sans dupliquer le mappeur ou déclarer une autre variable locale. Tout cela rend le code plus compliqué que le problème à résoudre.

Je préférerais beaucoup écrire ceci:

return firstChoice().alternatively(secondChoice());

Cependant, Facultatif :: de toute évidence, cela n'existe pas. Maintenant quoi?

31
Sebastian Oberhoff

Essaye ça:

firstChoice().map(Optional::of)
             .orElseGet(this::secondChoice);

La méthode de la carte vous donne un Optional<Optional<Foo>>. Ensuite, la méthode orElseGet l'aplatit pour la transformer en Optional<Foo>. La méthode secondChoice ne sera évaluée que si firstChoice() renvoie l'élément facultatif vide.

38
marstran

Vous pouvez simplement remplacer cela par,

Optional<Foo> firstChoice = firstChoice();
return firstChoice.isPresent()? firstChoice : secondChoice();

Le code ci-dessus n'appellera que si firstChoice.isPresent () est false.

Mais vous devez être prêt à appeler les deux fonctions pour obtenir le résultat souhaité. Il n'y a pas d'autre moyen d'échapper à la vérification.

  • Le meilleur des cas est que Premier choix revienne vrai.
  • Le cas le plus défavorable sera Premier choix renvoyant false, d'où une autre méthode. Appeler pour le deuxième choix.
5
Suresh Atta

Voici la généralisation de la solution @marstran pour n’importe quel nombre d’options:

@SafeVarargs
public static <T> Optional<T> selectOptional(Supplier<Optional<T>>... optionals) {
    return Arrays.stream(optionals)
            .reduce((s1, s2) -> () -> s1.get().map(Optional::of).orElseGet(s2))
            .orElse(Optional::empty).get();
}

Tester:

public static Optional<String> first() {
    System.out.println("foo called");
    return Optional.empty();
}

public static Optional<String> second() {
    System.out.println("bar called");
    return Optional.of("bar");
}

public static Optional<String> third() {
    System.out.println("baz called");
    return Optional.of("baz");
}

public static void main(String[] args) {
    System.out.println(selectOptional(() -> first(), () -> second(), () -> third()));
}

Sortie:

foo called
bar called
Optional[bar]
4
Tagir Valeev

Peut-être quelque chose comme ça:

Optional<String> finalChoice = Optional.ofNullable(firstChoice()
    .orElseGet(() -> secondChoice()
    .orElseGet(() -> null)));

De: Chaînage des options en Java 8

3
Marthym

J'étais assez frustré par le fait que cela ne soit pas supporté par Java 8, que je sois revenu aux options de goyave qui ont or :

public abstract Optional<T> or(Optional<? extends T> secondChoice)

Renvoie cette option si elle a une valeur présente; secondChoice sinon.

1
Erik

Calculs différés et nombre arbitraire d'éléments Optional

Stream.<Supplier<Optional<Foo>>>of(
        this::firstChoice,
        this::secondChoice
).map(
        Supplier::get
).filter(
        Optional::isPresent
).findFirst(
).orElseGet(
    Optional::empty
);
0
Pepe-Soft