Stile funzionale di Java. Optional.ifPresent e if-not-Present?


274

In Java 8, voglio fare qualcosa su un Optionaloggetto se è presente e fare un'altra cosa se non è presente.

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

Questo non è uno "stile funzionale", comunque.

Optionalha un ifPresent()metodo, ma non riesco a concatenare un orElse()metodo.

Pertanto, non posso scrivere:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

In risposta a @assylias, non credo funzioni Optional.map()per il seguente caso:

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

In questo caso, quando optè presente, aggiorno la sua proprietà e salvo nel database. Quando non è disponibile, ne creo uno nuovo obje lo salvo nel database.

Nota nei due lambda che devo tornare null.

Ma quando optè presente, verranno eseguiti entrambi i lambda. objverrà aggiornato e un nuovo oggetto verrà salvato nel database. Ciò è dovuto return nullalla prima lambda. E orElseGet()continuerà a essere eseguito.


53
Usa il tuo primo campione. E ' bello .
Sotirios Delimanolis,

3
Ti suggerisco di smettere di forzare determinati comportamenti quando usi un'API non progettata per quel comportamento. Il tuo primo esempio mi sembra perfetto a parte alcune piccole considerazioni di stile, ma quelle sono considerate.
Skiwi,

4
@smallufo: sostituisci return null;con return o;(entrambi). Tuttavia, ho la netta sensazione che tu stia lavorando nel posto sbagliato. Dovresti lavorare sul sito che l'ha prodotto Optional. In quel luogo dovrebbe esserci un modo per eseguire l'operazione desiderata senza l'intermedio Optional.
Holger,

10
Java 9 implementa una soluzione per il tuo problema: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
Penso che il motivo per cui non è possibile farlo facilmente sia apposta. Opzionale non dovrebbe fare il controllo del flusso, ma piuttosto la trasformazione del valore. So che ifPresentciò contraddice questo. Tutti gli altri metodi si riferiscono al valore e non alle azioni.
AlikElzin-Kilaka,

Risposte:


109

Per me la risposta di @Dane White è OK, prima non mi piaceva usare Runnable ma non sono riuscito a trovare alternative, qui un'altra implementazione ho preferito di più

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

Poi :

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s ->System.out.println("isPresent "+s))
            .ifNotPresent(() -> System.out.println("! isPresent"));

Aggiornamento 1:

la soluzione di cui sopra per il modo tradizionale di sviluppo quando si ha il valore e si desidera elaborarlo, ma cosa succede se voglio definire la funzionalità e l'esecuzione sarà quindi, controllare il miglioramento sotto;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

Quindi potrebbe essere usato come:

    Consumer<Optional<Integer>> c=OptionalConsumer.of(System.out::println, ()->{System.out.println("Not fit");});
    IntStream.range(0, 100).boxed().map(i->Optional.of(i).filter(j->j%2==0)).forEach(c);

In questo nuovo codice hai 3 cose:

  1. può definire la funzionalità prima dell'esistente dell'oggetto facile.
  2. non creando la rifrazione degli oggetti per ogni Opzionale, solo uno, hai così meno memoria e meno GC.
  3. sta implementando il consumatore per un migliore utilizzo con altri componenti.

a proposito ora il suo nome è più descrittivo in realtà è Consumatore>


3
Dovrebbe usare Optional.ofNullable (o) invece di Optional.of (o)
traeper

2
È necessario utilizzare Nullable se non si è sicuri se il valore che si intende utilizzare ha null o no e non è necessario affrontare NPE, e nel caso in cui si sia sicuri che non sia null o non ci si preoccupi se get NPE.
Bassem Reda Zohdy

1
Penso che la classe OptionalConsumer abbia un aspetto migliore di if / else nel codice. Grazie! :)
witek1902,

207

Se si utilizza Java 9+, è possibile utilizzare il ifPresentOrElse()metodo:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

3
Bello perché è quasi pulito come il pattern matching in Scala
sscarduzio,

Due lambda del genere sono piuttosto brutti. Penso che / else sia molto più pulito in questi casi.
john16384,

1
@ john16384 OK, se lo trovi brutto, allora eliminerò la mia risposta (no).
Zheka Kozlov

Questo è molto carino, ma la domanda era specifica per JDK8 perché ifPresentOrElse non è disponibile.
hreinn,

81

Java 9 introduce

ifPresentOrElse se è presente un valore, esegue l'azione specificata con il valore, altrimenti esegue l'azione data vuota.

Vedi eccellente opzionale nel cheat sheet di Java 8 .

Fornisce tutte le risposte per la maggior parte dei casi d'uso.

Breve riassunto di seguito

ifPresent () - fare qualcosa quando è impostato Opzionale

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter () - rifiuta (filtra) alcuni valori opzionali.

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map () - trasforma il valore se presente

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse () / orElseGet () - svuotando Facoltativo al valore predefinito T

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow () - lancia pigramente eccezioni su vuoto Opzionale

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

66
Questo in realtà non risponde alla domanda di OP. Risponde a molti usi comuni ma non a ciò che ha chiesto l'OP.
Captain Man,

1
@CaptainMan in realtà lo fa; l'espressione opt.map ("found"). orElse ("not found") riempie il conto.
Matt,

4
@Matt no, OP richiede specificamente azioni da eseguire quando è facoltativo / non è presente, per non restituire un valore quando lo è o non lo è. OP menziona anche qualcosa di simile nella domanda usando orElseGet spiegando perché non funzionerà.
Captain Man,

2
@CaptainMan Vedo il tuo punto. Penso che potrebbe farlo funzionare se non restituisse null da map, ma è un po 'strano chiedere una soluzione funzionale in modo da poter chiamare un DAO. Mi sembra che avrebbe più senso restituire l'oggetto aggiornato / nuovo da questo map.orElseblocco e quindi fare ciò che è necessario fare con l'oggetto restituito.
Matt,

1
Penso che mapconcentrarsi sul flusso stesso e non sia destinato a "fare cose a un altro oggetto a seconda dello stato di questo elemento nel flusso". Buono a sapersi che ifPresentOrElseviene aggiunto in Java 9.
WesternGun

53

Un'alternativa è:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

Non penso che migliora la leggibilità però.

O come suggerito Marko, usa un operatore ternario:

System.out.println(opt.isPresent() ? "Found" : "Not found");

2
Grazie @assylias, ma non credo che Optional.map () funzioni per il caso (vedi aggiornamento del mio contesto).
smallufo,

2
@smallufo Dovresti tornare new Object();nella tua prima lambda, ma ad essere sincero diventa molto brutto. Vorrei attenermi a un if / else per il tuo esempio aggiornato.
assylias,

D'accordo, usare mapsolo Optionalper tornare per il concatenamento rende il codice più difficile da capire mentre mapsi presume che sia letteralmente associato a qualcosa.
Tiina,

40

Un'altra soluzione sarebbe quella di utilizzare le funzioni di ordine superiore come segue

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

8
Ai miei occhi la soluzione migliore finora senza JDK 9.
Semafora

5
Una spiegazione sarebbe fantastica. Mi chiedo, perché è necessario utilizzare una mappa eseguibile (?) E cosa value -> () -> sysosignifica la parte.
froehli,

Grazie per questa soluzione! Penso che il motivo dell'utilizzo di Runnable sia che out map non restituisce alcun valore e con Runnable restituisce lambda e come risultato di map è lambda, lo eseguiamo dopo. Quindi, se hai un valore di ritorno, puoi utilizzare quanto segue:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

Non c'è un ottimo modo per farlo fuori dalla scatola. Se si desidera utilizzare la sintassi più pulita su base regolare, è possibile creare una classe di utilità per aiutare:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

Quindi puoi utilizzare un'importazione statica altrove per ottenere una sintassi vicina a ciò che stai cercando:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));

Grazie. Questa soluzione è bellissima. So che forse non esiste una soluzione integrata (a meno che JDK non includa tale metodo). OpzionaleEx è molto utile. Comunque grazie.
smallufo,

Sì, mi piace il risultato e lo stile che supporta. Quindi, perché non nell'API standard?
Guthrie,

Buona risposta. Facciamo lo stesso. Sono d'accordo che dovrebbe essere nell'API (o nella lingua!), Ma è stato rifiutato: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith

Bello. Questo dovrebbe essere parte del JDK 8.1 per considerazione.
peter_pilgrim,

35
Optional.ifPresentOrElse()è stato aggiunto a JDK 9.
Stuart Marks

9

Se è possibile utilizzare solo Java 8 o precedente:

1) se non hai spring-datafinora il modo migliore è:

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

Ora so che non è così leggibile e nemmeno difficile da capire per qualcuno, ma mi sembra a posto per me personalmente e non vedo un altro modo fluente per questo caso.

2) se sei abbastanza fortunato e puoi usare spring-datail modo migliore è Optionals # ifPresentOrElse :

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

Se puoi usare Java 9, dovresti assolutamente andare con:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));

2

Il comportamento descritto può essere ottenuto usando Vavr (precedentemente noto come Javaslang), una libreria oggetto-funzionale per Java 8+, che implementa la maggior parte dei costrutti Scala (essendo Scala un linguaggio più espressivo con un sistema di tipo più ricco basato su JVM). È un'ottima libreria da aggiungere ai tuoi progetti Java per scrivere codice funzionale puro.

Vavr fornisce la Optionmonade che fornisce funzioni per lavorare con il tipo Opzione come:

  • fold: per mappare il valore dell'opzione su entrambi i casi (definito / vuoto)
  • onEmpty: consente di eseguire un Runnablequando l'opzione è vuota
  • peek: consente di consumare il valore dell'opzione (quando definita).
  • ed è anche Serializableal contrario il Optionalche significa che puoi tranquillamente usarlo come argomento del metodo e membro di istanza.

L'opzione segue le leggi della monade in modo diverso dalla "pseudo-monade" facoltativa di Java e fornisce un'API più ricca. E ovviamente puoi farlo da un Java opzionale (e viceversa): Option.ofOptional(javaOptional)–Vavr è focalizzato sull'interoperabilità.

Andando all'esempio:

// AWESOME Vavr functional collections (immutable for the gread good :)
// fully convertible to Java's counterparts.
final Map<String, String> map = Map("key1", "value1", "key2", "value2");

final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!

final String result = opt.fold(
        () -> "Not found!!!",                // Option is None
        val -> "Found the value: " + val     // Option is Some(val)
);

Ulteriori letture

Riferimento nullo, errore di miliardi di dollari

NB Questo è solo un piccolo esempio di ciò che offre Vavr (pattern matching, stream aka liste valutate pigre, tipi monadici, raccolte immutabili, ...).


1

Un'altra soluzione potrebbe essere la seguente:

Ecco come lo usi:

    final Opt<String> opt = Opt.of("I'm a cool text");
    opt.ifPresent()
        .apply(s -> System.out.printf("Text is: %s\n", s))
        .elseApply(() -> System.out.println("no text available"));

O nel caso in cui il caso d'uso opposto sia vero:

    final Opt<String> opt = Opt.of("This is the text");
    opt.ifNotPresent()
        .apply(() -> System.out.println("Not present"))
        .elseApply(t -> /*do something here*/);

Questi sono gli ingredienti:

  1. Interfaccia di funzione leggermente modificata, solo per il metodo "elseApply"
  2. Miglioramento opzionale
  3. Un po 'di curring :-)

L'interfaccia funzionale "esteticamente" migliorata.

@FunctionalInterface
public interface Fkt<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }

}

E la classe wrapper opzionale per il miglioramento:

public class Opt<T> {

    private final Optional<T> optional;

    private Opt(final Optional<T> theOptional) {
        this.optional = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

    private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
        if (this.optional.isPresent()) {
            present.accept(this.optional.get());
        } else {
            notPresent.run();
        }
        return null;
    };

   private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.optional.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.optional.get());
        }
        return null;
    };

    public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

    private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
        return (final X x) -> (final Y y) -> function.apply(x, y);
    }
}

Questo dovrebbe fare il trucco e potrebbe servire da modello di base su come gestire tali requisiti.

L'idea di base qui sta seguendo. In un mondo di programmazione di stile non funzionale probabilmente implementereste un metodo che prende due parametri in cui il primo è un tipo di codice eseguibile che dovrebbe essere eseguito nel caso in cui il valore sia disponibile e l'altro parametro sia il codice eseguibile che dovrebbe essere eseguito nel caso in cui il il valore non è disponibile. Per motivi di migliore leggibilità, è possibile utilizzare il curring per dividere la funzione di due parametri in due funzioni di un parametro ciascuno. Questo è quello che ho praticamente fatto qui.

Suggerimento: Opt fornisce anche l'altro caso d'uso in cui si desidera eseguire un pezzo di codice nel caso in cui il valore non sia disponibile. Questo potrebbe essere fatto anche tramite Optional.filter.stuff ma l'ho trovato molto più leggibile.

Spero che aiuti!

Buona programmazione :-)


Puoi dire cosa non funziona? L'ho provato di nuovo e per me funziona?
Alessandro Giusa,

Mi scusi, ma dove è definito il ckass Fkt? Ultimo ma non meno importante, ho un problema di compilazione: Errore: (35, 17) java: la variabile facoltativa potrebbe non essere stata inizializzata
Enrico Giurin,

Fkt è definito sopra come interfaccia. Basta leggere l'intero articolo :-)
Alessandro Giusa,

Sì. Ho cambiato l'interfaccia EFunction in Fkt come nome. C'è stato un errore di battitura. Grazie per la recensione :-) scusa per quello.
Alessandro Giusa,

Non credo sia una buona idea scrivere codice in questo modo ... Dovresti usare l'utilità jdk quando puoi.
Tyulpan Tyulpan,

0

Nel caso in cui desideri memorizzare il valore:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));

0

Supponendo di avere un elenco ed evitare il isPresent()problema (relativo agli optionals), è possibile utilizzare .iterator().hasNext()per verificare se non presente.

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.