Funktionsstil von Java 8 Optional.ifPresent und if-not-Present?

274

In Java 8 möchte ich etwas mit einem OptionalObjekt tun, wenn es vorhanden ist, und etwas anderes tun, wenn es nicht vorhanden ist.

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

Dies ist jedoch kein "funktionaler Stil".

Optionalhat eine ifPresent()Methode, aber ich kann keine orElse()Methode verketten.

Daher kann ich nicht schreiben:

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

Als Antwort auf @assylias denke ich nicht, dass dies Optional.map()für den folgenden Fall funktioniert:

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 diesem Fall optaktualisiere ich , wenn vorhanden, seine Eigenschaft und speichere in der Datenbank. Wenn es nicht verfügbar ist, erstelle ich eine neue objund speichere sie in der Datenbank.

Beachten Sie in den beiden Lambdas, dass ich zurückkehren muss null.

Aber wenn optvorhanden, werden beide Lambdas ausgeführt. objwird aktualisiert und ein neues Objekt wird in der Datenbank gespeichert. Dies liegt am return nullim ersten Lambda. Und orElseGet()wird weiterhin ausgeführt.

smallufo
quelle
53
Verwenden Sie Ihre erste Probe. Es ist schön .
Sotirios Delimanolis
3
Ich schlage vor, dass Sie bestimmte Verhaltensweisen nicht mehr erzwingen, wenn Sie eine API verwenden, die nicht für dieses Verhalten ausgelegt ist. Ihr erstes Beispiel sieht für mich gut aus, abgesehen von einigen kleinen Stilbemerkungen, aber diese sind meinungsbildend.
Skiwi
4
@smallufo: Ersetzen return null;mit return o;(beide). Ich habe jedoch das starke Gefühl, dass Sie am falschen Ort arbeiten. Sie sollten an der Stelle arbeiten, an der das hergestellt wurde Optional. An dieser Stelle sollte es eine Möglichkeit geben, die gewünschte Operation ohne Zwischenprodukt durchzuführen Optional.
Holger
10
Java 9 implementiert eine Lösung für Ihr Problem: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Ich denke, der Grund, warum dies nicht einfach gemacht werden kann, ist absichtlich. Optional sollte keine Flusskontrolle durchgeführt werden, sondern eine Werttransformation. Ich weiß, das ifPresentwiderspricht dem. Alle anderen Methoden beziehen sich auf den Wert und nicht auf Aktionen.
AlikElzin-Kilaka

Antworten:

109

Für mich ist die Antwort von @Dane White OK, zuerst mochte ich Runnable nicht, aber ich konnte keine Alternativen finden, hier eine andere Implementierung, die ich mehr bevorzugte

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

Dann :

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

Update 1:

Die obige Lösung für die traditionelle Art der Entwicklung, wenn Sie den Wert haben und ihn verarbeiten möchten, aber was ist, wenn ich die Funktionalität und die Ausführung definieren möchte, überprüfen Sie unten die Verbesserung.

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

Dann könnte verwendet werden als:

    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 diesem neuen Code haben Sie 3 Dinge:

  1. kann Funktionalität definieren, bevor das Objekt einfach existiert.
  2. Wenn Sie keine Objektreferenz für jede Option erstellen, nur eine, haben Sie so weniger Speicher als weniger GC.
  3. Es implementiert Consumer für eine bessere Verwendung mit anderen Komponenten.

Übrigens ist sein Name jetzt aussagekräftiger, er ist tatsächlich Verbraucher>

Bassem Reda Zohdy
quelle
3
Sollte Optional.ofNullable (o) anstelle von Optional.of (o) verwenden
Traeper
2
Sie müssen ofNullable verwenden, wenn Sie nicht sicher sind, ob der Wert, den Sie verwenden möchten, null hat oder nicht und nicht mit NPE konfrontiert werden muss, und wenn Sie sicher sind, dass es nicht null ist oder es Ihnen egal ist, ob get NPE.
Bassem Reda Zohdy
1
Ich denke, dass die Klasse OptionalConsumer besser aussieht als if / else im Code. Vielen Dank! :)
witek1902
207

Wenn Sie Java 9+ verwenden, können Sie folgende ifPresentOrElse()Methode verwenden:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
quelle
3
Schön, weil es fast so sauber ist wie der Mustervergleich in Scala
sscarduzio
Zwei solche Lambdas sind ziemlich hässlich. Ich denke, wenn / sonst für diese Fälle viel sauberer ist.
John16384
1
@ john16384 OK, wenn du es hässlich findest, werde ich meine Antwort löschen (nein).
ZhekaKozlov
Das ist sehr schön, aber die Frage war speziell für JDK8 gedacht, da ifPresentOrElse nicht verfügbar ist.
Hreinn
81

Java 9 wird eingeführt

ifPresentOrElse führt, wenn ein Wert vorhanden ist, die angegebene Aktion mit dem Wert aus, andernfalls führt die angegebene leere Aktion aus.

Siehe ausgezeichnet Optional in Java 8 Spickzettel .

Es bietet alle Antworten für die meisten Anwendungsfälle.

Kurze Zusammenfassung unten

ifPresent () - etwas tun, wenn Optional eingestellt ist

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

filter () - bestimmte optionale Werte ablehnen (herausfiltern).

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

map () - Wert transformieren, falls vorhanden

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

orElse () / orElseGet () - leer werden Optional auf Standard T.

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

orElseThrow () - träge Ausnahmen auf leer werfen Optional

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);
Bartosz Bilicki
quelle
66
Dies beantwortet die Frage von OP nicht wirklich. Es beantwortet viele gängige Verwendungen, aber nicht das, was OP verlangt hat.
Captain Man
1
@CaptainMan tatsächlich tut es; Der Ausdruck opt.map ("gefunden"). orElse ("nicht gefunden") füllt die Rechnung.
Matt
4
@Matt nein, OP bittet speziell um Aktionen, die ausgeführt werden sollen, wenn das Optionale vorhanden ist / nicht vorhanden ist, um keinen Wert zurückzugeben, wenn es vorhanden ist oder nicht. OP erwähnt in der Frage sogar etwas Ähnliches mit orElseGet und erklärt, warum es nicht funktioniert.
Captain Man
2
@ CaptainMan Ich verstehe deinen Standpunkt. Ich denke, er könnte es zum Laufen bringen, wenn er nicht null von der zurückgibt map, aber es ist ein bisschen seltsam, nach einer funktionalen Lösung zu fragen, damit Sie einen DAO anrufen können. Mir scheint, es wäre sinnvoller, das aktualisierte / neue Objekt aus diesem map.orElseBlock zurückzugeben und dann das zu tun, was Sie mit dem zurückgegebenen Objekt tun müssen.
Matt
1
Ich denke, mapkonzentrieren Sie sich auf den Stream selbst und sind nicht dazu gedacht, "Dinge mit einem anderen Objekt zu tun, abhängig vom Status dieses Elements im Stream". Gut zu wissen, dass ifPresentOrElsein Java 9 hinzugefügt wird.
WesternGun
53

Eine Alternative ist:

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

Ich denke nicht, dass es die Lesbarkeit verbessert.

Oder verwenden Sie, wie von Marko vorgeschlagen, einen ternären Operator:

System.out.println(opt.isPresent() ? "Found" : "Not found");
Assylien
quelle
2
Vielen Dank an @assylias, aber ich glaube nicht, dass Optional.map () für den Fall funktioniert (siehe mein Kontext-Update).
Smallufo
2
@smallufo Du müsstest new Object();in deinem ersten Lambda zurückkehren, aber um ehrlich zu sein, wird das sehr hässlich. Ich würde mich an ein if / else für Ihr aktualisiertes Beispiel halten.
Assylias
Stimmen Sie zu, wenn Sie mapnur Optionalzur Verkettung zurückkehren, wird der Code schwieriger zu verstehen, während mapdavon ausgegangen wird, dass er buchstäblich auf etwas abgebildet wird.
Tiina
40

Eine andere Lösung wäre, Funktionen höherer Ordnung wie folgt zu verwenden

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
user5057016
quelle
8
In meinen Augen die bisher beste Lösung ohne JDK 9.
Semaphor
5
Eine Erklärung wäre toll. Ich frage mich, warum Sie eine ausführbare Karte (?) Verwenden müssen und was der value -> () -> sysoTeil bedeutet.
Froehli
Danke für diese Lösung! Ich denke, der Grund für die Verwendung von Runnable ist, dass out map keinen Wert zurückgibt und mit Runnable Lambda zurückgibt. Das Ergebnis der Karte ist Lambda, nach dem wir es ausführen. Wenn Sie also einen Rückgabewert haben, können Sie Folgendes verwenden:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik
21

Es gibt keine großartige Möglichkeit, dies sofort zu tun. Wenn Sie Ihre Cleaner-Syntax regelmäßig verwenden möchten, können Sie eine Utility-Klasse erstellen, um Folgendes zu unterstützen:

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

Dann können Sie einen statischen Import an anderer Stelle verwenden, um eine Syntax zu erhalten, die genau dem entspricht, wonach Sie suchen:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));
Däne Weiß
quelle
Vielen Dank. Diese Lösung ist wunderschön. Ich weiß, dass es möglicherweise keine integrierte Lösung gibt (es sei denn, JDK enthält eine solche Methode). Sie OptionalEx ist sehr hilfreich. Trotzdem danke.
Smallufo
Ja, ich mag das Ergebnis und den Stil, den es unterstützt. Warum also nicht in der Standard-API?
Guthrie
Gute Antwort. Wir machen das gleiche. Ich bin damit einverstanden, dass es in der API (oder Sprache!) Sein sollte , aber es wurde abgelehnt: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith
Nett. Dies sollte zur Prüfung Teil des JDK 8.1 sein.
Peter_pilgrim
35
Optional.ifPresentOrElse()wurde zu JDK 9 hinzugefügt.
Stuart Marks
9

Wenn Sie nur Java 8 oder niedriger verwenden können:

1) Wenn Sie bisher nicht spring-dataden besten Weg haben, ist:

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

Jetzt weiß ich, dass es für jemanden nicht so lesbar und sogar schwer zu verstehen ist, aber für mich persönlich sieht es gut aus und ich sehe keinen anderen schönen fließenden Weg für diesen Fall.

2) Wenn Sie Glück haben und spring-dataden besten Weg nutzen können , ist Optionals # ifPresentOrElse :

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

Wenn Sie Java 9 verwenden können, sollten Sie auf jeden Fall Folgendes wählen:

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

Das beschriebene Verhalten kann mit erreicht werden Vavr (früher bekannt als Javaslang) erreicht werden, einer objektfunktionalen Bibliothek für Java 8+, die die meisten Scala-Konstrukte implementiert (Scala ist eine ausdrucksstärkere Sprache mit einem wesentlich umfangreicheren Typsystem, das auf JVM basiert). Es ist eine sehr gute Bibliothek, die Sie zu Ihren Java-Projekten hinzufügen können, um reinen Funktionscode zu schreiben.

Vavr bietet die OptionMonade, die Funktionen für die Arbeit mit dem Optionstyp bereitstellt , z.

  • fold: um den Wert der Option in beiden Fällen zuzuordnen (definiert / leer)
  • onEmpty: Ermöglicht die Ausführung einer RunnableWhen-Option, wenn diese leer ist
  • peek: Ermöglicht die Verwendung des Werts der Option (falls definiert).
  • und es ist auch Serializableim Gegenteil, Optionalwas bedeutet, dass Sie es sicher als Methodenargument und Instanzmitglied verwenden können.

Option folgt den Monadengesetzen, die sich von der optionalen "Pseudo-Monade" von Java unterscheiden, und bietet eine umfangreichere API. Und natürlich können Sie es aus einem Java-Optional (und umgekehrt) machen:Option.ofOptional(javaOptional) –Vavr konzentriert sich auf Interoperabilität.

Zum Beispiel gehen:

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

Weiterführende Literatur

Null Referenz, der Milliarden-Dollar-Fehler

NB Dies ist nur ein sehr kleines Beispiel für das, was Vavr bietet (Mustervergleich, Streams, auch bekannt als faul bewertete Listen, monadische Typen, unveränderliche Sammlungen, ...).

Gerard Bosch
quelle
1

Eine andere Lösung könnte sein:

So verwenden Sie es:

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

Oder falls Sie im Falle des entgegengesetzten Anwendungsfalls wahr sind:

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

Dies sind die Zutaten:

  1. Wenig modifizierte Funktionsschnittstelle, nur für die "elseApply" -Methode
  2. Optionale Erweiterung
  3. Ein bisschen Curring :-)

Die "kosmetisch" verbesserte Funktionsschnittstelle.

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

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

}

Und die optionale Wrapper-Klasse zur Verbesserung:

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

Dies sollte den Trick tun und könnte als grundlegende Vorlage für den Umgang mit solchen Anforderungen dienen.

Die Grundidee hier folgt. In einer nicht funktionalen Programmierwelt würden Sie wahrscheinlich eine Methode implementieren, die zwei Parameter verwendet, wobei der erste eine Art ausführbarer Code ist, der ausgeführt werden sollte, wenn der Wert verfügbar ist, und der andere Parameter der ausführbare Code ist, der ausgeführt werden sollte, wenn der Wert ist nicht verfügbar. Zur besseren Lesbarkeit können Sie die Funktion von zwei Parametern mithilfe von curring in zwei Funktionen mit jeweils einem Parameter aufteilen. Das habe ich hier im Grunde getan.

Hinweis: Opt bietet auch den anderen Anwendungsfall, in dem Sie einen Code ausführen möchten, falls der Wert nicht verfügbar ist. Dies könnte auch über Optional.filter.stuff erfolgen, aber ich fand dies viel besser lesbar.

Ich hoffe, das hilft!

Gute Programmierung :-)

Alessandro Giusa
quelle
Kannst du sagen, was nicht funktioniert? Ich habe es erneut getestet und für mich funktioniert es?
Alessandro Giusa
Entschuldigung, aber wo ist der ckass Fkt definiert? Zu guter Letzt habe ich ein Kompilierungsproblem: Fehler: (35, 17) Java: Variable optional wurde möglicherweise nicht initialisiert
Enrico Giurin
Fkt ist oben als Schnittstelle definiert.
Lies
Ja. Ich habe die EFunction-Schnittstelle in Fkt als Namen geändert. Es gab einen Tippfehler. Danke für die Bewertung :-) Entschuldigung.
Alessandro Giusa
Ich denke nicht, dass es eine gute Idee ist, Code wie diesen zu schreiben ... Sie sollten das Dienstprogramm jdk verwenden, wenn Sie können.
Tyulpan Tyulpan
0

Falls Sie den Wert speichern möchten:

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

Angenommen, Sie haben eine Liste und vermeiden das isPresent()Problem (im Zusammenhang mit Optionen), mit dem Sie .iterator().hasNext()überprüfen können, ob es nicht vorhanden ist.

Leandro Maro
quelle