[JAVA] Wählen Sie die erste nicht leere aus mehreren Optional aus und rufen Sie ihre Methode auf

Hintergrund

Es ist schwer zu denken, nachdem "Schreiben eines Nullfalls mit optionalem Typ ohne Verwendung der if-Anweisung" angezeigt wurde.

Dinge die zu tun sind

Es gibt eine solche Schnittstelle

public interface Foo {
    public abstract String foo();
}
public interface Bar {
    public abstract String bar();
}
public interface Buzz {
    public abstract String buzz();
}

Beziehungsweise

Optional<Foo> foo = FooImpl.getInstance();
Optional<Bar> bar = BarImpl.getInstance();
Optional<Buzz> buzz = BuzzImpl.getInstance();

Und so weiter. Suchen Sie nacheinander nach nicht leeren Objekten, z. B. Balken, wenn foo leer ist, Summen, wenn Balken ebenfalls leer ist, und rufen Sie die Methode des ersten nicht leeren Objekts auf, das den Wert annimmt. Wenn alle leer sind, legen Sie einen geeigneten Standardwert fest.

if-else / ternärer Operator

String value;
if(foo.isPresent()){
    value = foo.foo();
} else if(bar.isPresent()){
    value = bar.bar();
} else if(buzz.isPresent()){
    value = buzz.buzz();
} else {
    value = "empty";
}

Es ist eine alte Art zu schreiben. Mit dem ternären Operator können Sie fast dasselbe schreiben.

String value =
    foo.isPresent() ? foo.foo() : 
    bar.isPresent() ? bar.bar() : 
    buzz.isPresent() ? buzz.buzz() : "empty";

Gefällt dir welches du wählst?

Optional#orElse() / orElseGet() Wenn Sie es gehorsam mit orElse () schreiben, sieht es so aus.

String value =
    foo.map(Foo::foo).orElse(
        bar.map(Bar::bar).orElse(
            buzz.map(Buzz::buzz).orElse("empty")));

Dies ist nicht unlesbar, aber je mehr Sie überprüfen, desto tiefer wird die Verschachtelung. Die Frage ist also, wie Sie einrücken können.

String value = foo.map(Foo::foo).orElse(
               bar.map(Bar::bar).orElse(
               buzz.map(Buzz::buzz).orElse("empty")));

Oder

String value = foo.map(Foo::foo)
       .orElse(bar.map(Bar::bar)
       .orElse(buzz.map(Buzz::buzz)
       .orElse("empty")));

Ist es besser, es so zu formen? Ähnliches gilt für orElseGet ()

String value = foo.map(Foo::foo).orElseGet(() -> 
               bar.map(Bar::bar).orElseGet(() -> 
               buzz.map(Buzz::buzz).orElseGet(() ->
               "empty")));

Oder

String value = foo.map(Foo::foo)
              .orElseGet(() -> bar.map(Bar::bar)
              .orElseGet(() -> buzz.map(Buzz::buzz)
              .orElseGet(() -> "empty")));

Wenn ja, kann ich es wohl nicht lesen ... Wenn diese Art des Schreibens als Redewendung populär wird, denke ich, dass dies in Ordnung ist. Ich kann das komplizierte Gefühl jedoch nicht ohne die Syntax der Zuckerbeschichtung leugnen.

Deklarativ verarbeiten

Lassen Sie uns die Denkweise ändern und darüber nachdenken, ob sie deklarativ behandelt werden kann. Betrachten wir der Klarheit halber zunächst einen einzelnen Typ.

Optional<Foo> foo1 = FooImpl.getInstance();
Optional<Foo> foo2 = FooImpl.getInstance();
Optional<Foo> foo3 = FooImpl.getInstance();
String value = Stream.of(foo1, foo2, foo3)
                     .filter(Optional::isPresent)
                     //Wenn Sie in einer Zeile schreiben.map(o -> o.get().foo())
                     .map(Optional::get)
                     .map(Foo::foo)
                     .findFirst()
                     .orElse("empty");

Das ist einfach, nicht wahr? Wenn es möglich ist, die ursprünglichen Schnittstellen (Foo, Bar, Buzz) zu ändern und es vernünftig erscheint, eine gemeinsame Super-Schnittstelle für sie zu haben, wäre eine solche Korrektur eine gute Idee. Da findFirst () eine Kurzschlussoperation ist, wird die Zuordnung nur für das erste vom Filter übereinstimmende Objekt durchgeführt, und es wird kein unnötiger Wert abgerufen.

Wenn Sie verschiedene Typen so behandeln möchten, wie sie sind, müssen Sie sie einem gemeinsamen Typ zuordnen, bevor Sie einen Stream erstellen?

String value = Stream.of(foo.map(Foo::foo), bar.map(Bar::bar), buzz.map(Buzz::buzz))
                     .filter(Optional::isPresent)
                     .map(Optional::get)
                     .findFirst()
                     .orElse("empty");

Der Nachteil dieser Methode besteht darin, dass der Aufruf der Terminalmethode zum Zeitpunkt der Zuordnung noch erfolgt. Bar # bar () und Buzz # summ () sollten vermieden werden, wenn es sich um kostspielige Vorgänge handelt. Eine Art Wrapping ist erforderlich, um das Mapping zu verzögern ...

//Sie müssen den Typ angeben, der in Lambda übergeben werden soll
String value = Stream.<Supplier<Optional<String>>>of(
                        () -> foo.map(Foo::foo),
                        () -> bar.map(Bar::bar),
                        () -> buzz.map(Buzz::buzz))
                     .map(Supplier::get)
                     .filter(Optional::isPresent)
                     .map(Optional::get)
                     .findFirst()
                     .orElseGet(() -> "empty");

Es ist ein heikler Ort.

Verallgemeinern

@SafeVarargs
public static <R> R findFirstOrElseGet(
        Supplier<R> defaultSupplier,
        Supplier<Optional<R>>... suppliers) {

    return Stream.of(suppliers)
                 .map(Supplier::get)
                 .filter(Optional::isPresent)
                 .map(Optional::get)
                 .findFirst()
                 .orElseGet(defaultSupplier);
}

Wenn Sie eine solche Methode vorbereiten

String value = findFirstOrElseGet(
                        () -> "empty",
                        () -> foo.map(Foo::foo),
                        () -> bar.map(Bar::bar),
                        () -> buzz.map(Buzz::buzz));

Das ist in Ordnung, aber es kann eine Übertreibung für das sein, was Sie trotzdem tun möchten.

Hinweise zur optionalen # map ()

Beachten Sie, dass sich die Verarbeitung mit diesen optionalen # map () von der Version von if-else und dem ternären Operator unterscheidet. Selbst wenn Foo # foo () usw. null zurückgibt, ist der unter orElse () angegebene Wert derselbe. Ich komme wieder. Mit anderen Worten, in Bezug auf wenn

String value = null;
if(foo.isPresent()){
    value = foo.foo();
}
if(value == null && bar.isPresent()){
    value = bar.bar();
}
if(value == null && buzz.isPresent()){
    value = buzz.buzz();
}
if(value == null) {
    value = "empty";
}

Bitte beachten Sie, dass der Prozess in der Nähe ist.

Persönliches Resümee

--if-else / Der ternäre Operator ist auf einem niedrigen Niveau, aber derzeit ist das Know-how allgegenwärtig, und es besteht fast keine Notwendigkeit, innerhalb des Teams zu quetschen. --Optional # orElse () / orElseGet () ist die Verschachtelung in Bezug auf die Lesbarkeit dem Operator if-else / ternary etwas unterlegen, aber Sie können dies tun, ohne sich bis zum Ende um Nullen kümmern zu müssen. --Deklarative Verarbeitung in Stream ist leistungsstark, wenn die Typkonvertierung nicht teuer ist.

Ich möchte eine Implementierung auswählen, die von Fall zu Fall korrekt zu sein scheint.

Recommended Posts

Wählen Sie die erste nicht leere aus mehreren Optional aus und rufen Sie ihre Methode auf
Filtern Sie die optionale Java No. 2-Liste, die im Geschäftsleben nützlich war, und erhalten Sie den ersten Wert
Implementierungsmethode zum Verknüpfen mehrerer Bilder mit einem Beitrag und zum gleichzeitigen Posten
Ich möchte eine Methode aufrufen und die Nummer zählen
[Kotlin] Holen Sie sich Java Constructor / Method von KFunction und rufen Sie es auf
Extrahieren Sie effizient mehrere Elemente zufällig und ohne Duplizierung aus der Liste