Java 8 - Différence entre Optional.flatMap et Optional.map

162

Quelle est la différence entre ces deux méthodes: Optional.flatMap()et Optional.map()?

Un exemple serait apprécié.

codépendant
la source
5
@AlexisC. Votre lien concerne la carte et la flatMap de Stream, et non facultatif.
Eran
1
@Eran Cela n'a pas d'importance, si vous comprenez comment fonctionne map / flatMap, que ce soit pour un Stream ou non, c'est la même chose pour un Optional. Si l'op a compris comment cela fonctionne pour un Stream, alors il ne devrait pas poser cette question. Le concept est le même.
Alexis C.
2
@AlexisC. Pas vraiment. Le flatMap en option a peu en commun avec le flatMap de Stream.
Eran
1
@Eran Je parle de la différence conceptuelle entre une map et une flatMap, je ne fais pas de correspondance un-à-un entre Stream#flatMapet Optional#flatMap.
Alexis C.

Réponses:

166

À utiliser mapsi la fonction renvoie l'objet dont vous avez besoin ou flatMapsi la fonction renvoie un Optional. Par exemple:

public static void main(String[] args) {
  Optional<String> s = Optional.of("input");
  System.out.println(s.map(Test::getOutput));
  System.out.println(s.flatMap(Test::getOutputOpt));
}

static String getOutput(String input) {
  return input == null ? null : "output for " + input;
}

static Optional<String> getOutputOpt(String input) {
  return input == null ? Optional.empty() : Optional.of("output for " + input);
}

Les deux instructions d'impression impriment la même chose.

assylies
la source
5
Question: appellerait [flat]Mapjamais la fonction de cartographie avec un input == null? Je crois comprendre que les Optionalcoupes de tri s'il est absent - le [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) semble le confirmer - « Si une valeur est présente, appliquez. . ".
Boris the Spider
1
@BoristheSpider Optional.of (null)! = Optional.empty ()
Diego Martinoia
14
@DiegoMartinoia Optional.of(null)est un Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider
1
@BoristheSpider oui, vous avez raison. J'essayais de répondre à votre question mais je pense que je l'ai rendue encore plus floue: conceptuellement, Optional.ofNullable (null) ne devrait PAS être vide, mais en pratique, il est considéré comme, et donc map / flatmap ne s'exécute pas.
Diego Martinoia
1
Je pense que l'entrée ne devrait jamais être nulle dans getOutputOpt ou getOutput
DanyalBurke
55

Ils prennent tous deux une fonction du type de l'option à quelque chose.

map()applique la fonction " tel quel " sur l'option que vous avez:

if (optional.isEmpty()) return Optional.empty();
else return Optional.of(f(optional.get()));

Que se passe-t-il si votre fonction est une fonction de T -> Optional<U>?
Votre résultat est maintenant un Optional<Optional<U>>!

C'est de cela qu'il flatMap()s'agit: si votre fonction retourne déjà un Optional, flatMap()est un peu plus intelligente et ne la double pas, retourne Optional<U>.

C'est la composition de deux idiomes fonctionnels: mapet flatten.

Diego Martinoia
la source
7

Remarque: - ci-dessous se trouve l'illustration de la fonction map et flatmap, sinon Facultatif est principalement conçu pour être utilisé comme type de retour uniquement.

Comme vous le savez peut-être déjà, Facultatif est un type de conteneur qui peut ou non contenir un seul objet, il peut donc être utilisé partout où vous prévoyez une valeur nulle (vous ne verrez peut-être jamais NPE si vous utilisez Facultatif correctement). Par exemple, si vous avez une méthode qui attend un objet person qui peut être nullable, vous pouvez écrire la méthode quelque chose comme ceci:

void doSome(Optional<Person> person){
  /*and here you want to retrieve some property phone out of person
    you may write something like this:
  */
  Optional<String> phone = person.map((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}
class Person{
  private String phone;
  //setter, getters
}

Ici, vous avez renvoyé un type String qui est automatiquement enveloppé dans un type facultatif.

Si la classe de personne ressemblait à ceci, le téléphone est également facultatif

class Person{
  private Optional<String> phone;
  //setter,getter
}

Dans ce cas, l'appel de la fonction map encapsulera la valeur retournée dans Optional et produira quelque chose comme:

Optional<Optional<String>> 
//And you may want Optional<String> instead, here comes flatMap

void doSome(Optional<Person> person){
  Optional<String> phone = person.flatMap((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}

PS; N'appelez jamais la méthode get (si vous en avez besoin) sur un Optional sans le vérifier avec isPresent () sauf si vous ne pouvez pas vivre sans NullPointerExceptions.

SandeepGodara
la source
1
Je pense que cet exemple est susceptible de détourner l'attention de la nature de votre réponse parce que votre classe Personfait un mauvais usage Optional. Il est contre l'intention de l'API d'utiliser Optionalsur des membres comme celui-ci - voir mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/…
8bitjunkie
@ 8bitjunkie Merci d'avoir souligné cela, cela diffère de l'option de Scala ..
SandeepGodara
6

Ce qui m'a aidé, c'est un regard sur le code source des deux fonctions.

Map - encapsule le résultat dans une option.

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value)); //<--- wraps in an optional
    }
}

flatMap - renvoie l'objet 'brut'

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value)); //<---  returns 'raw' object
    }
}
Robert Niestroj
la source
1
Qu'entendez-vous par flatMap"renvoie l'objet" brut ""? flatMaprenvoie également l'objet mappé «enveloppé» dans un fichier Optional. La différence est que dans le cas de flatMap, la fonction mapper enveloppe l'objet mappé dans Optionaltandis que maplui-même enveloppe l'objet Optional.
Derek Mahar
@DerekMahar a supprimé le mien, inutile de le re-publier, car vous avez bien édité votre commentaire.
maxxyme
3
  • Optional.map():

Prend chaque élément et si la valeur existe, elle est passée à la fonction:

Optional<T> optionalValue = ...;
Optional<Boolean> added = optionalValue.map(results::add);

Maintenant ajouté a l'une des trois valeurs suivantes: trueou falseencapsulé dans un facultatif , s'il optionalValueétait présent, ou un facultatif vide dans le cas contraire.

Si vous n'avez pas besoin de traiter le résultat que vous pouvez simplement utiliser ifPresent(), il n'a pas de valeur de retour:

optionalValue.ifPresent(results::add); 
  • Optional.flatMap():

Fonctionne de manière similaire à la même méthode de flux. Aplatit le flux de flux. A la différence que si la valeur est présentée, elle est appliquée à la fonction. Sinon, un optionnel vide est renvoyé.

Vous pouvez l'utiliser pour composer des appels de fonctions de valeur facultatives.

Supposons que nous ayons des méthodes:

public static Optional<Double> inverse(Double x) {
    return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

public static Optional<Double> squareRoot(Double x) {
    return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}

Ensuite, vous pouvez calculer la racine carrée de l'inverse, comme:

Optional<Double> result = inverse(-4.0).flatMap(MyMath::squareRoot);

ou, si vous préférez:

Optional<Double> result = Optional.of(-4.0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);

Si le inverse()ou les squareRoot()retours Optional.empty(), le résultat est vide.

nazar_art
la source
1
Cela ne compile pas. Vos deux expressions renvoient un <Double> facultatif plutôt que le double auquel vous affectez le résultat.
JL_SO
@JL_SO vous avez raison. Parce que inverse a le Optional<Double>type comme type de retour.
nazar_art
3

D'accord. Vous n'avez besoin d'utiliser 'flatMap' que lorsque vous faites face à des options imbriquées . Voici l'exemple.

public class Person {

    private Optional<Car> optionalCar;

    public Optional<Car> getOptionalCar() {
        return optionalCar;
    }
}

public class Car {

    private Optional<Insurance> optionalInsurance;

    public Optional<Insurance> getOptionalInsurance() {
        return optionalInsurance;
    }
}

public class Insurance {

    private String name;

    public String getName() {
        return name;
    }

}

public class Test {

    // map cannot deal with nested Optionals
    public Optional<String> getCarInsuranceName(Person person) {
        return person.getOptionalCar()
                .map(Car::getOptionalInsurance) // ① leads to a Optional<Optional<Insurance>
                .map(Insurance::getName);       // ②
    }

}

Comme Stream, Optional # map renverra une valeur encapsulée par un Optional. C'est pourquoi nous obtenons une option imbriquée - Optional<Optional<Insurance>. Et à ②, nous voulons la cartographier comme une instance d'assurance, c'est ainsi que la tragédie s'est produite. La racine est des options imbriquées. Si nous pouvons obtenir la valeur fondamentale indépendamment des coquilles, nous y arriverons. C'est ce que fait flatMap.

public Optional<String> getCarInsuranceName(Person person) {
    return person.getOptionalCar()
                 .flatMap(Car::getOptionalInsurance)
                 .map(Insurance::getName);
}

En fin de compte, je vous recommande vivement Java 8 In Action si vous souhaitez étudier Java8 systématiquement.

momonannan
la source