Facultatif ou facultatif en Java

J’ai travaillé avec le nouveau type facultatif dans Java 8 , et j’ai rencontré ce qui semble être une opération commune qui n’est pas supscope fonctionnellement: un “orElseOptional”

Considérons le modèle suivant:

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

Il existe de nombreuses formes de ce modèle, mais cela revient à 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 orElse(Supplier<Optional> 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 simplement Optionnel d’une manière involontaire, et quelles sont les autres façons dont les gens ont proposé de traiter cette affaire.

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

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

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

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

Pour plus de détails, voir le Javadoc ou cet article que j’ai écrit.

L’approche la plus propre des «services d’essai» étant donné que l’API actuelle serait:

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

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

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

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 . Si cette Optional contient la valeur v , nous voulons obtenir Optional.of(v) , mais si elle est vide, nous voulons obtenir le serviceB(args) . Répétez le rinçage avec plus d’alternatives.

D’autres utilisations de ce motif sont

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

Peut-être est-ce ce que vous recherchez: obtenir la valeur d’une option ou d’une autre

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

 result = Optional.ofNullable(serviceA().orElseGet( () -> serviceB().orElseGet( () -> serviceC().orElse(null)))); 

En supposant que vous êtes toujours sur JDK8, il y a plusieurs options.

Option n ° 1: créez votre propre méthode d’aide

Par exemple:

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

Pour que tu puisses faire:

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

Option n ° 2: utiliser une bibliothèque

Par exemple, facultatif, google guava prend en charge une opération correcte or() (tout comme JDK9), par exemple:

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

(Où chacun des services renvoie com.google.common.base.Optional plutôt que java.util.Optional ).

Cela ressemble à une adaptation de modèle et à une interface Option plus traditionnelle avec des implémentations Some et None (telles que celles de Javaslang , FunctionalJava ) ou une implémentation paresseuse dans cyclops-react. Je suis l’auteur de cette bibliothèque.

Avec cyclops-react, vous pouvez également utiliser la correspondance de modèles structurels sur les types JDK. Pour facultatif, vous pouvez faire correspondre les cas présents et absents via le modèle de 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)));