Il est difficile de penser après avoir vu "Ecrire une casse nulle en utilisant le type facultatif sans utiliser l'instruction if".
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.
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.
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.
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.
--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