Style fonctionnel d'Optionalif.ifPresent et if-not-Present de Java 8?

274

En Java 8, je veux faire quelque chose à un Optionalobjet s'il est présent, et faire autre chose s'il n'est pas présent.

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

Ce n'est cependant pas un «style fonctionnel».

Optionala une ifPresent()méthode, mais je ne peux pas enchaîner une orElse()méthode.

Ainsi, je ne peux pas écrire:

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

En réponse à @assylias, je ne pense pas que cela Optional.map()fonctionne pour le cas suivant:

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

Dans ce cas, lorsqu'il optest présent, je mets à jour sa propriété et l'enregistre dans la base de données. Lorsqu'il n'est pas disponible, j'en crée un nouveau objet l'enregistre dans la base de données.

Remarque dans les deux lambdas je dois revenir null.

Mais quand il optest présent, les deux lambdas seront exécutés. objsera mis à jour et un nouvel objet sera enregistré dans la base de données. C'est à cause de return nullla première lambda. Et orElseGet()continuera à exécuter.

smallufo
la source
53
Utilisez votre premier échantillon. C'est magnifique .
Sotirios Delimanolis
3
Je vous suggère d'arrêter de forcer certains comportements lorsque vous utilisez une API qui n'est pas conçue pour ce comportement. Votre premier exemple me semble bien mis à part quelques petites remarques de style, mais celles-ci sont exprimées.
skiwi
4
@smallufo: remplacez return null;par return o;(les deux). Cependant, j'ai le fort sentiment que vous travaillez au mauvais endroit. Vous devriez travailler sur le site qui a produit cela Optional. À cet endroit, il devrait y avoir un moyen d'effectuer l'opération souhaitée sans l'intermédiaire Optional.
Holger
10
Java 9 implémente une solution à votre problème: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Je pense que la raison pour laquelle cela ne peut pas être fait facilement est exprès. Facultatif ne doit pas faire de contrôle de flux, mais plutôt de transformation de valeur. Je sais que ifPresentcela contredit. Toutes les autres méthodes font référence à la valeur et non aux actions.
AlikElzin-kilaka

Réponses:

109

Pour moi, la réponse de @Dane White est OK, d'abord je n'ai pas aimé utiliser Runnable mais je n'ai trouvé aucune alternative, ici une autre implémentation que j'ai préféré plus

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

Ensuite :

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

Mise à jour 1:

la solution ci-dessus pour le mode de développement traditionnel lorsque vous avez la valeur et que vous souhaitez la traiter mais que se passe-t-il si je veux définir la fonctionnalité et l'exécution sera alors, vérifiez l'amélioration ci-dessous;

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

Ensuite, pourrait être utilisé comme:

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

Dans ce nouveau code, vous avez 3 choses:

  1. peut définir une fonctionnalité avant d'exister facilement.
  2. ne pas créer de réfraction d'objet pour chaque Facultatif, un seul, vous avez donc moins de mémoire que moins de GC.
  3. il met en œuvre le consommateur pour une meilleure utilisation avec d'autres composants.

à propos maintenant son nom est plus descriptif, il est en fait consommateur>

Bassem Reda Zohdy
la source
3
Devrait utiliser Optional.ofNullable (o) au lieu de Optional.of (o)
traeper
2
Vous devez utiliser ofNullable si vous n'êtes pas sûr si la valeur que vous allez utiliser est nulle ou non et que vous n'avez pas besoin de faire face à NPE, et si vous êtes sûr qu'elle n'est pas nulle ou si vous ne vous souciez pas d'obtenir NPE.
Bassem Reda Zohdy
1
Je pense que la classe OptionalConsumer est meilleure que si / else dans le code. Merci! :)
witek1902
207

Si vous utilisez Java 9+, vous pouvez utiliser la ifPresentOrElse()méthode:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
la source
3
Sympa parce que c'est presque aussi propre que la correspondance de motifs dans Scala
sscarduzio
Deux lambda comme ça, c'est plutôt moche. Je pense que si / autre est beaucoup plus propre pour ces cas.
john16384
1
@ john16384 OK, si vous le trouvez moche, alors je vais supprimer ma réponse (non).
ZhekaKozlov
C'est très bien mais la question était spécifiquement destinée au JDK8 car ifPresentOrElse n'est pas disponible.
hreinn
81

Java 9 présente

ifPresentOrElse si une valeur est présente, exécute l'action donnée avec la valeur, sinon exécute l'action basée sur le vide donnée.

Voir excellente feuille de triche facultative dans Java 8 .

Il fournit toutes les réponses pour la plupart des cas d'utilisation.

Bref résumé ci-dessous

ifPresent () - faire quelque chose lorsque Facultatif est défini

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

filter () - rejeter (filtrer) certaines valeurs facultatives.

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

map () - transforme la valeur si elle est présente

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

orElse () / orElseGet () - tourner vide Facultatif à T par défaut

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

orElseThrow () - levée paresseuse des exceptions sur vide Facultatif

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);
Bartosz Bilicki
la source
66
Cela ne répond pas réellement à la question d'OP. Il répond à de nombreuses utilisations courantes, mais pas à ce que OP a demandé.
Captain Man
1
@CaptainMan en fait; l'expression opt.map ("trouvé"). orElse ("non trouvé") remplit la facture.
Matt
4
@Matt non, OP demande spécifiquement des actions à effectuer lorsque l'option est / n'est pas présente, pour ne pas retourner une valeur quand elle est ou n'est pas. OP mentionne même quelque chose de similaire dans la question en utilisant orElseGet expliquant pourquoi cela ne fonctionnera pas.
Captain Man
2
@CaptainMan Je vois votre point. Je pense qu'il pourrait le faire fonctionner s'il ne retournait pas null depuis le map, mais c'est un peu étrange de demander une solution fonctionnelle pour que vous puissiez appeler un DAO. Il me semble qu'il serait plus judicieux de renvoyer l'objet mis à jour / nouveau à partir de ce map.orElsebloc, puis de faire ce que vous devez faire avec l'objet renvoyé.
Matt
1
Je pense que se mapconcentrer sur le flux lui-même et n'est pas destiné à "faire des choses à un autre objet en fonction du statut de cet élément dans le flux". Bon à savoir qui ifPresentOrElseest ajouté dans Java 9.
WesternGun
53

Une alternative est:

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

Je ne pense pas que cela améliore la lisibilité.

Ou comme l'a suggéré Marko, utilisez un opérateur ternaire:

System.out.println(opt.isPresent() ? "Found" : "Not found");
assylias
la source
2
Merci @assylias, mais je ne pense pas que Optional.map () fonctionne pour le cas (voir ma mise à jour du contexte).
smallufo
2
@smallufo Vous auriez besoin de revenir new Object();dans votre premier lambda mais pour être honnête cela devient très moche. Je m'en tiendrai à un if / else pour votre exemple mis à jour.
assylias
D'accord, l'utilisation mapde simplement revenir Optionalpour le chaînage rend le code plus difficile à comprendre alors que l' mapon suppose qu'il correspond littéralement à quelque chose.
Tiina
40

Une autre solution serait d'utiliser des fonctions d'ordre supérieur comme suit

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
user5057016
la source
8
À mes yeux, la meilleure solution à ce jour sans JDK 9.
Semaphor
5
Une explication serait formidable. Je me demande pourquoi vous devez utiliser une carte exécutable (?) Et ce que value -> () -> sysosignifie la partie.
froehli
Merci pour cette solution! Je pense que la raison d'utiliser Runnable est que out map ne retourne aucune valeur et avec Runnable il retourne lambda et tel que le résultat de map est lambda nous l'exécutons après. Donc, si vous avez une valeur de retour, vous pouvez utiliser ce qui suit:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik
21

Il n'y a pas un excellent moyen de le faire hors de la boîte. Si vous souhaitez utiliser régulièrement votre syntaxe de nettoyage, vous pouvez créer une classe utilitaire pour vous aider:

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

Ensuite, vous pouvez utiliser une importation statique ailleurs pour obtenir une syntaxe proche de ce que vous recherchez:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));
Dane White
la source
Merci. Cette solution est belle. Je sais qu'il n'y a peut-être pas de solution intégrée (sauf si JDK incorpore une telle méthode). Vous OptionalEx est très utile. Quoi qu'il en soit, merci.
smallufo
Oui, j'aime le résultat et le style qu'il supporte. Alors, pourquoi pas dans l'API standard?
guthrie
Bonne réponse. Nous faisons de même. Je suis d'accord qu'il devrait être dans l'API (ou la langue!), Mais il a été refusé: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith
Agréable. Cela devrait faire partie du JDK 8.1 pour examen.
peter_pilgrim
35
Optional.ifPresentOrElse()a été ajouté au JDK 9.
Stuart Marks
9

Si vous ne pouvez utiliser que Java 8 ou une version antérieure:

1) si vous ne disposez pas spring-datadu meilleur moyen jusqu'à présent:

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

Maintenant, je sais que ce n'est pas si lisible et même difficile à comprendre pour quelqu'un, mais ça me va bien personnellement et je ne vois pas une autre belle manière fluide pour ce cas.

2) si vous êtes assez chanceux et que vous pouvez utiliser spring-datale meilleur moyen est Optionals # ifPresentOrElse :

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

Si vous pouvez utiliser Java 9, vous devez absolument utiliser:

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

Le comportement décrit peut être obtenu en utilisant Vavr (anciennement Javaslang), une bibliothèque fonctionnelle pour Java 8+, qui implémente la plupart des constructions Scala (Scala étant un langage plus expressif avec un système de type beaucoup plus riche basé sur JVM). C'est une très bonne bibliothèque à ajouter à vos projets Java pour écrire du code fonctionnel pur.

Vavr fournit la Optionmonade qui fournit des fonctions pour travailler avec le type d'option telles que:

  • fold: pour mapper la valeur de l'option sur les deux cas (défini / vide)
  • onEmpty: permet d'exécuter une Runnableoption quand est vide
  • peek: permet de consommer la valeur de l'option (lorsqu'elle est définie).
  • et c'est aussi Serializableau contraire Optionalce qui signifie que vous pouvez l'utiliser en toute sécurité comme argument de méthode et membre d'instance.

L'option suit les lois de la monade à la différence de la "pseudo-monade" facultative de Java et fournit une API plus riche. Et bien sûr, vous pouvez le faire à partir d'un Java facultatif (et Option.ofOptional(javaOptional)inversement ): –Vavr se concentre sur l'interopérabilité.

En passant à l'exemple:

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

Lectures complémentaires

Référence nulle, l'erreur d'un milliard de dollars

NB Ce n'est qu'un très petit exemple de ce que propose Vavr (correspondance de modèles, streams ou listes évaluées paresseuses, types monadiques, collections immuables, ...).

Gerard Bosch
la source
1

Une autre solution pourrait être la suivante:

Voici comment vous l'utilisez:

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

Ou dans le cas où vous dans le cas d'utilisation contraire est vrai:

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

Ce sont les ingrédients:

  1. Petite interface Function modifiée, juste pour la méthode "elseApply"
  2. Amélioration facultative
  3. Un peu de ronronnement :-)

L'interface de fonction améliorée "cosmétiquement".

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

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

}

Et la classe wrapper facultatif pour l'amélioration:

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

Cela devrait faire l'affaire et pourrait servir de modèle de base pour gérer ces exigences.

L'idée de base ici est la suivante. Dans un monde de programmation de style non fonctionnel, vous implémenteriez probablement une méthode prenant deux paramètres où le premier est une sorte de code exécutable qui devrait être exécuté au cas où la valeur est disponible et l'autre paramètre est le code exécutable qui devrait être exécuté au cas où le la valeur n'est pas disponible. Dans un souci de meilleure lisibilité, vous pouvez utiliser le récurage pour diviser la fonction de deux paramètres en deux fonctions d'un paramètre chacune. C'est ce que j'ai essentiellement fait ici.

Astuce: Opt fournit également l'autre cas d'utilisation où vous souhaitez exécuter un morceau de code au cas où la valeur ne serait pas disponible. Cela pourrait être fait également via Optional.filter.stuff mais j'ai trouvé cela beaucoup plus lisible.

J'espère que cela pourra aider!

Bonne programmation :-)

Alessandro Giusa
la source
Pouvez-vous dire ce qui ne fonctionne pas? Je l'ai testé à nouveau et pour moi ça marche?
Alessandro Giusa
Excusez-moi, mais où est défini le ckass Fkt? Dernier point mais non le moindre, j'ai un problème de compilation: Erreur: (35, 17) java: la variable facultative n'a peut-être pas été initialisée
Enrico Giurin
Fkt est défini ci-dessus comme interface. Il suffit de lire l'intégralité de l'article :-)
Alessandro Giusa
Oui. J'ai changé l'interface EFunction en Fkt comme nom. Il y avait une faute de frappe. Merci pour l'examen :-) désolé pour cela.
Alessandro Giusa
Je ne pense pas que ce soit une bonne idée d'écrire du code comme celui-ci ... Vous devriez utiliser l'utilitaire jdk quand vous le pouvez.
Tyulpan Tyulpan
0

Si vous souhaitez stocker la valeur:

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

Supposons que vous ayez une liste et évitez le isPresent()problème (lié aux options) que vous pourriez utiliser .iterator().hasNext()pour vérifier s'il n'est pas présent.

Leandro Maro
la source