[JAVA] Sélectionnez le premier non vide parmi plusieurs facultatifs et appelez sa méthode

Contexte

Il est difficile de penser après avoir vu "Ecrire une casse nulle en utilisant le type facultatif sans utiliser l'instruction if".

Choses à faire

Il y a une interface comme celle-ci

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

Chaque

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

Etc. Recherchez les objets non vides dans l'ordre, tels que bar si foo est vide, buzz si bar est également vide, et appelez la méthode du premier objet non vide pour prendre la valeur. Si tous sont vides, définissez une valeur par défaut appropriée.

if-else / opérateur ternaire

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

C'est une vieille façon d'écrire. Vous pouvez écrire presque la même chose avec l'opérateur ternaire.

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

Aimez-vous celui que vous choisissez?

Optional#orElse() / orElseGet() Si vous l'écrivez docilement avec orElse (), cela ressemblera à ceci.

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

Ce n'est pas illisible, mais plus vous vérifiez, plus l'imbrication devient profonde, donc la question est de savoir comment mettre en retrait.

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

Ou

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

Vaut-il mieux le façonner ainsi? De même pour orElseGet ()

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

Ou

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

Si oui, je pense que je ne peux pas le lire ... Si ce genre d'écriture devient populaire en tant qu'idiome, je pense que c'est bien. Cependant, je ne peux pas nier le sentiment compliqué sans la syntaxe d'enrobage de sucre.

Traiter de manière déclarative

Changeons la façon de penser et réfléchissons à la possibilité de la traiter de manière déclarative. Par souci de clarté, considérons d'abord un seul type.

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)
                     //Si vous écrivez sur une seule ligne.map(o -> o.get().foo())
                     .map(Optional::get)
                     .map(Foo::foo)
                     .findFirst()
                     .orElse("empty");

C'est facile, non? S'il est possible de modifier les interfaces d'origine (Foo, Bar, Buzz) et qu'il semble raisonnable d'avoir une super interface commune pour elles, alors une telle correction serait une bonne idée. Puisque findFirst () est une opération de court-circuit, le mappage est effectué uniquement pour le premier objet correspondant au filtre, et aucune valeur inutile n'est récupérée.

Si vous souhaitez gérer différents types tels quels, devez-vous les mapper à un type commun avant de créer un flux?

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

L'inconvénient de cette méthode est que l'appel de la méthode du terminal est toujours effectué au moment du mappage. Bar # bar () et Buzz # buzz () doivent être évités s'il s'agit d'opérations coûteuses. Une sorte d'emballage est nécessaire pour retarder le mappage ...

//Vous devez spécifier le type à passer en lambda
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");

C'est un endroit délicat.

Généraliser

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

Si vous préparez une telle méthode

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

C'est bien, mais cela peut être une exagération pour ce que vous voulez faire de toute façon.

Remarques sur la carte # facultative ()

Notez que le traitement utilisant ces Optional # map () est différent de la version de if-else et de l'opérateur ternaire, et même lorsque Foo # foo () etc. renvoie null, la valeur spécifiée sous orElse () est la même. Je reviendrai. En d'autres termes, en termes de si

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

Veuillez noter que le processus est proche de.

Résumé personnel

--if-else / L'opérateur ternaire est à un niveau bas, mais à l'heure actuelle le savoir-faire est omniprésent, et il n'y aura presque pas besoin de serrer au sein de l'équipe.

Je voudrais choisir une mise en œuvre qui me semble correcte au cas par cas.

Recommended Posts

Sélectionnez le premier non vide parmi plusieurs facultatifs et appelez sa méthode
Filtrez la liste facultative Java n ° 2 utile en entreprise et obtenez la première valeur
Méthode de mise en œuvre consistant à lier plusieurs images à un article et à publier en même temps
Je veux appeler une méthode et compter le nombre
[Kotlin] Obtenez le constructeur / la méthode Java de KFunction et appelez-le
Extraire efficacement plusieurs éléments de la liste de manière aléatoire et sans duplication