Opzionale oElse Opzionale in Java

Ho lavorato con il nuovo tipo Optional in Java 8 , e mi sono imbattuto in ciò che sembra un’operazione comune che non è supportata funzionalmente: un “orElseOptional”

Considera il seguente schema:

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

Ci sono molte forms di questo modello, ma si riduce a volere un “orElse” su un opzionale che prende una funzione producendo un nuovo opzionale, chiamato solo se quello attuale non esiste.

La sua implementazione sarebbe simile a questa:

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

Sono curioso di sapere se esiste una ragione per cui un tale metodo non esiste, se sto semplicemente usando Opzionale in modo non intenzionale e quali altri modi le persone hanno escogitato per affrontare questo caso.

Devo dire che penso che le soluzioni che coinvolgono classi / metodi di utilità personalizzati non siano eleganti perché le persone che lavorano con il mio codice non sapranno necessariamente che esistono.

Inoltre, se qualcuno lo sa, tale metodo sarà incluso in JDK 9 e dove potrei proporre un tale metodo? Mi sembra un’omissione evidente all’API.

Questo fa parte di JDK 9 sotto forma di or , che richiede un Supplier> . Il tuo esempio sarebbe quindi:

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

Per dettagli vedi Javadoc o questo post che ho scritto.

L’approccio “try services” più pulito data l’attuale API sarebbe:

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

L’aspetto importante non è la (costante) catena di operazioni che devi scrivere una volta, ma quanto è facile aggiungere un altro servizio (o modificare l’elenco dei servizi è generale). Qui, aggiungere o rimuovere un singolo ()->serviceX(args) è sufficiente.

A causa della valutazione lenta degli stream, nessun servizio verrà invocato se un servizio precedente ha restituito un Optional non vuoto.

Non è carino, ma funzionerà:

 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) è un modello abbastanza comodo da usare con Optional . Significa “Se questo Optional contiene valore v , dammi func(v) , altrimenti dammi sup.get() “.

In questo caso, chiamiamo serviceA(args) e otteniamo un Optional . Se Optional contiene il valore v , vogliamo ottenere Optional.of(v) , ma se è vuoto, vogliamo ottenere il serviceB(args) . Risciacquare-ripetere con più alternative.

Altri usi di questo modello sono

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

Forse questo è ciò che stai cercando: ottieni valore da un Optional o da un altro

Altrimenti, potresti voler dare un’occhiata a Optional.orElseGet . Ecco un esempio di ciò che penso che cerchi:

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

Supponendo che tu sia ancora su JDK8, ci sono diverse opzioni.

Opzione 1: crea il tuo metodo di supporto

Per esempio:

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

In modo che tu possa fare:

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

Opzione 2: usa una biblioteca

Ad esempio, google guava Opzionale supporta un’operazione corretta or() (proprio come JDK9), ad esempio:

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

(Dove ognuno dei servizi restituisce com.google.common.base.Optional , piuttosto che java.util.Optional ).

Questo sembra un buon adattamento per la corrispondenza dei pattern e un’interfaccia Option più tradizionale con implementazioni Some e None (come quelle in Javaslang , FunctionalJava ) o un’implementazione pigra Forse in cyclops-react. Sono l’autore di questa libreria.

Con cyclops-react è anche ansible utilizzare la corrispondenza del modello strutturale sui tipi JDK. Per Opzionale è ansible abbinare i casi presenti e assenti tramite il modello visitatore . assomiglierebbe a questo –

  import static com.aol.cyclops.Matchables.optional; optional(serviceA(args)).visit(some -> some , () -> optional(serviceB(args)).visit(some -> some, () -> serviceC(args)));