Opzionale oElse Opzionale in Java


137

Ho lavorato con il nuovo tipo opzionale in Java 8 e ho riscontrato quella che sembra un'operazione comune che non è supportata funzionalmente: un "orElseOptional"

Considera il seguente modello:

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);
}

Esistono molte forme di questo modello, ma si riduce a desiderare un "orElse" su un opzionale che accetta una funzione producendo un nuovo opzionale, chiamato solo se quello attuale non esiste.

La sua implementazione sarebbe simile a questa:

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

Sono curioso di sapere se esiste un motivo per cui un tale metodo non esiste, se sto semplicemente usando Opzionale in modo non intenzionale e quali altri modi sono stati inventati per affrontare questo caso.

Dovrei 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, un tale metodo sarà incluso in JDK 9 e dove potrei proporlo? Mi sembra un'omissione piuttosto evidente all'API.


13
Vedi questo numero . Per chiarire: questo sarà già in Java 9 - se non in un futuro aggiornamento di Java 8.
Obicere

È! Grazie, non l'ho trovato nella mia ricerca.
Yona Appletree

2
@Obicere Questo problema non si applica qui perché riguarda il comportamento su vuoto Opzionale, non un risultato alternativo . Opzionale ha già orElseGet()ciò di cui ha bisogno OP, solo che non genera una bella sintassi a cascata.
Marko Topolnik,


Risposte:


86

Questo fa parte di JDK 9 nella forma di or, che prende a Supplier<Optional<T>>. Il tuo esempio sarebbe quindi:

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

Per i dettagli vedere Javadoc o questo post che ho scritto.


Bello. Quell'aggiunta deve avere un anno e non me ne sono accorta. Per quanto riguarda la domanda nel tuo blog, la modifica del tipo di restituzione comprometterebbe la compatibilità binaria, poiché le istruzioni di invocazione bytecode si riferiscono alla firma completa, incluso il tipo di restituzione, quindi non è possibile modificare il tipo di restituzione di ifPresent. Comunque, penso che il nome ifPresentnon sia comunque buono. Per tutti gli altri metodi non recanti “altro” nel nome (come map, filter, flatMap), è implicito che non fanno nulla se nessun valore è presente, quindi perché dovrebbe ifPresent...
Holger

Quindi aggiungendo un Optional<T> perform(Consumer<T> c)metodo per consentire il concatenamento perform(x).orElseDo(y)( orElseDoin alternativa alla proposta ifEmpty, essere coerenti nel elsenome di tutto il metodo che potrebbe fare qualcosa per valori assenti). Puoi imitarlo performin Java 9 tramite stream().peek(x).findFirst()sebbene sia un abuso dell'API e non c'è ancora modo di eseguire un Runnablesenza specificare un Consumerallo stesso tempo ...
Holger

65

L'approccio più pulito ai "servizi di prova" data l'API corrente sarebbe:

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'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, ()->serviceX(args)è sufficiente aggiungere o rimuovere un singolo .

A causa della valutazione lenta dei flussi, nessun servizio verrà richiamato se un servizio precedente ha restituito un valore non vuoto Optional.


13
L'ho appena usato in un progetto, grazie a dio non facciamo recensioni di codice.
Ilya Smagin,

3
È molto più pulito di una catena di "orElseGet", ma è anche molto più difficile da leggere.
slartidan,

4
Questo è certamente corretto ... ma onestamente, mi ci vuole un secondo per analizzarlo e non me ne vado con la certezza che sia corretto. Intendiamoci, mi rendo conto che questo esempio lo è, ma potrei immaginare un piccolo cambiamento che non lo farebbe valutare pigramente o avrebbe qualche altro errore che sarebbe indistinguibile a colpo d'occhio. Per me, questo rientra nella categoria delle funzioni di utilità decente.
Yona Appletree,

3
Mi chiedo se il passaggio .map(Optional::get)con .findFirst()renderà più facile la "lettura", ad esempio .filter(Optional::isPresent).findFirst().map(Optional::get)può essere "letto" come "trovare il primo elemento nello stream per cui è facoltativo :: isPresent e quindi appiattirlo applicando Opzione :: get"?
schatten,

3
Divertente, ho pubblicato una soluzione molto simile per una domanda simile qualche mese fa. Questa è la prima volta che mi imbatto in questo.
shmosel,

34

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 utile da usare con Optional. Significa "Se questo Optionalcontiene valore v, dammi func(v), altrimenti dammi sup.get()".

In questo caso, chiamiamo serviceA(args)e riceviamo un Optional<Result>. Se questo Optionalcontiene valore v, vogliamo ottenere Optional.of(v), ma se è vuoto, vogliamo ottenere serviceB(args). Risciacqua-ripeti con più alternative.

Altri usi di questo modello sono

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

1
eh, quando uso questa strategia, eclipse dice: "Il metodo orElseGet (Supplier <? extends String>) nel tipo Facoltativo <String> non è applicabile per gli argomenti (() -> {})" Non sembra considerare la restituzione di un opzionale una strategia valida, sulla stringa ??
Chrismarx,

@chrismarx () -> {}non restituisce un Optional. Cosa stai cercando di realizzare?
Misha,

1
Sto solo cercando di seguire l'esempio. Il concatenamento delle chiamate della mappa non funziona. I miei servizi restituiscono stringhe, e ovviamente non c'è nessuna opzione .map () disponibile allora
chrismarx,

1
Eccellente alternativa leggibile all'imminente or(Supplier<Optional<T>>)Java 9
David M.

1
@Sheepy ti sbagli. .map()su un vuoto Optionalprodurrà un vuoto Optional.
Misha,

27

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

Altrimenti, potresti dare un'occhiata Optional.orElseGet. Ecco un esempio di ciò che penso che tu stia cercando:

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

2
Questo è ciò che fanno di solito i geni. Valutare l'opzione opzionale come nullable e racchiuderla ofNullableè la cosa più bella che abbia mai visto.
Jin Kwon,

5

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

Opzione n. 1: crea il tuo metodo di supporto

Per esempio:

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);
    }
}

In modo che tu possa fare:

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

Opzione n. 2: utilizzare una libreria

Ad esempio Opzionale di google guava supporta un corretto or()funzionamento (proprio come JDK9), ad esempio:

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

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


Non ho trovato Optional<T>.or(Supplier<Optional<T>>)nei documenti di Guava. Hai un link per quello?
Tamas Hegedus,

.orElseGet restituisce T ma Opzionale :: vuoto restituisce Opzionale <T>. Optional.ofNullable (........ orElse (null)) ha l'effetto desiderato come descritto da @aioobe.
Miguel Pereira,

@TamasHegedus Intendi nell'opzione n. 1? È un'implementazione personalizzata che si trova sopra di essa. ps: scusa per la risposta tardiva
Sheepy,

@MiguelPereira .orElseGet in questo caso restituisce <T> opzionale perché funziona su
<<<

2

Questo sembra un buon adattamento per il pattern matching e un'interfaccia Option più tradizionale con implementazioni Some e None (come quelle in Javaslang , FunctionalJava ) o un'implementazione pigra di forse in cyclops- reag. Sono l'autore di questa libreria.

Con il ciclope-reazione puoi anche usare la corrispondenza del modello strutturale sui tipi JDK. Per Opzionale puoi abbinare i casi presenti e assenti tramite il modello visitatore . sarebbe simile a questo -

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

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.