web-dev-qa-db-fra.com

Quelle est la différence entre les méthodes map et flatMap dans Java 8?

Dans Java 8, quelle est la différence entre les méthodes Stream.map et Stream.flatMap ?

589
cassiomolin

map et flatMap peuvent être appliqués à un Stream<T> et tous deux renvoient un Stream<R>. La différence est que l'opération map génère une valeur de sortie pour chaque valeur d'entrée, tandis que l'opération flatMap génère un nombre arbitraire (zéro ou plus) pour chaque valeur d'entrée.

Cela se reflète dans les arguments de chaque opération.

L'opération map prend un Function, appelé pour chaque valeur du flux d'entrée et produit une valeur de résultat, qui est envoyée au flux de sortie.

L'opération flatMap prend une fonction qui veut conceptuellement consommer une valeur et produire un nombre arbitraire de valeurs. Cependant, en Java, il est difficile pour une méthode de renvoyer un nombre arbitraire de valeurs, car les méthodes ne peuvent renvoyer que zéro ou une valeur. On pourrait imaginer une API dans laquelle la fonction de mappage pour flatMap prend une valeur et renvoie un tableau ou un List de valeurs, qui sont ensuite envoyées à la sortie. Etant donné qu'il s'agit de la bibliothèque de flux, un moyen particulièrement approprié de représenter un nombre arbitraire de valeurs de retour consiste pour la fonction de mappeur elle-même à renvoyer un flux! Les valeurs du flux renvoyées par le mappeur sont extraites du flux et transmises au flux de sortie. Les "groupes" de valeurs renvoyées par chaque appel de la fonction de mappeur ne sont pas du tout distingués dans le flux de sortie; par conséquent, la sortie est dite "aplatie".

La fonction de mappage de flatMap est généralement utilisée pour renvoyer Stream.empty() si elle souhaite envoyer des valeurs nulles ou quelque chose comme Stream.of(a, b, c) si elle souhaite renvoyer plusieurs valeurs. Mais bien sûr, tout flux peut être retourné.

709
Stuart Marks

Stream.flatMap, comme son nom l'indique, est la combinaison d'une opération map et d'une flat. Cela signifie que vous appliquez d'abord une fonction à vos éléments, puis que vous l'aplatissez. Stream.map applique uniquement une fonction au flux sans l'aplatir.

Pour comprendre en quoi aplatir un flux consiste, considérons une structure telle que [ [1,2,3],[4,5,6],[7,8,9] ] qui a "deux niveaux". Aplatir cela signifie le transformer en une structure "à un niveau": [ 1,2,3,4,5,6,7,8,9 ].

354
Dici

Je voudrais donner 2 exemples pour obtenir un plus point de vue pratique:
Premier exemple d'utilisation de la carte:

@Test
public void convertStringToUpperCaseStreams() {
    List<String> collected = Stream.of("a", "b", "hello") // Stream of String 
            .map(String::toUpperCase) // Returns a stream consisting of the results of applying the given function to the elements of this stream.
            .collect(Collectors.toList());
    assertEquals(asList("A", "B", "HELLO"), collected);
}   

Rien de spécial dans le premier exemple, un Function est appliqué pour renvoyer le String en majuscule.

Deuxième exemple d'utilisation de flatMap:

@Test
public void testflatMap() throws Exception {
    List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
            .flatMap(List::stream)
            .map(integer -> integer + 1)
            .collect(Collectors.toList());
    assertEquals(asList(2, 3, 4, 5), together);
}

Dans le deuxième exemple, un flux de liste est passé. Ce n'est pas un flux d'entiers!
Si une fonction de transformation doit être utilisée (via une carte), le flux doit d'abord être aplati en un autre élément (un flux d'entiers).
Si flatMap est supprimé, l'erreur suivante est renvoyée: l'opérateur + n'est pas défini pour le type d'argument (s), int.
Il n'est PAS possible d'appliquer +1 à une liste d'entiers!

210
Rudy Vissers

S'il vous plaît, passez complètement par la poste pour avoir une idée claire,

map vs flatMap:

Pour retourner une longueur de chaque mot d'une liste, nous ferions quelque chose comme ci-dessous.

Version courte donnée ci-dessous

Lorsque nous recueillons deux listes, données ci-dessous

Sans carte plate => [1,2], [1,1] => [[1,2], [1,1]] Ici deux listes sont placées dans une liste, ainsi le résultat sera une liste contenant des listes

avec carte plate => [1,2], [1,1] => [1,2,1,1] Ici, deux listes sont aplaties et seules les valeurs sont placées dans la liste. La sortie sera donc une liste contenant uniquement des éléments.

Fondamentalement, il fusionne tous les objets en un

## La version détaillée a été donnée ci-dessous: -

Par exemple: -
Considérons une liste [“STACK”, “OOOVVVER”] et nous essayons de renvoyer une liste comme [“STACKOVER”] (ne renvoyant que des lettres uniques de cette liste) Initialement, nous ferions quelque chose comme ci-dessous pour renvoyer une liste [“STACKOVER ”] de [“ PILE ”,“ OOOVVVER ”]

public class WordMap {
  public static void main(String[] args) {
    List<String> lst = Arrays.asList("STACK","OOOVER");
    lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList());
  }
}

Le problème est que Lambda, passé à la méthode map, renvoie un tableau de chaînes pour chaque mot. Le flux renvoyé par la méthode map est en fait de type Stream. Nous avons toutefois besoin de Stream pour représenter un flux de caractères. L'image ci-dessous illustre problème.

Figure A:

enter image description here

Vous pourriez penser que, nous pouvons résoudre ce problème en utilisant flatmap,
OK, voyons comment résoudre ce problème en utilisant map et Arrays.stream Tout d’abord, vous allez besoin d'un flux de caractères au lieu d'un flux de tableaux. Il existe une méthode appelée Arrays.stream () qui prend un tableau et génère un flux, par exemple:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting Word in to array of letters
    .map(Arrays::stream).distinct() //Make array in to separate stream
    .collect(Collectors.toList());

Ce qui précède ne fonctionne toujours pas, car nous avons maintenant une liste de flux (plus précisément, Stream>). Au lieu de cela, nous devons d'abord convertir chaque mot en un tableau de lettres individuelles, puis transformer chaque tableau en un flux séparé.

En utilisant flatMap, nous devrions pouvoir résoudre ce problème comme suit:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting Word in to array of letters
    .flatMap(Arrays::stream).distinct() //flattens each generated stream in to a single stream
    .collect(Collectors.toList());

flatMap effectuera le mappage de chaque tableau non pas avec le flux, mais avec le contenu de ce flux. Tous les flux individuels générés lors de l'utilisation de la carte (Arrays :: stream) sont fusionnés en un seul flux. La figure B illustre l'effet de l'utilisation de la méthode flatMap. Comparez-la avec la carte de la figure A. Figure B enter image description here

La méthode flatMap vous permet de remplacer chaque valeur d'un flux par un autre flux, puis de joindre tous les flux générés à un seul flux.

134
TechDog

Réponse en ligne: flatMap aide à aplatir un _Collection<Collection<T>>_ en un _Collection<T>_ . De la même manière, il va également aplatir un Optional<Optional<T>> en Optional<T>.

enter image description here

Comme vous pouvez le constater, avec map() uniquement:

  • Le type intermédiaire est _Stream<List<Item>>_
  • Le type de retour est _List<List<Item>>_

et avec flatMap():

  • Le type intermédiaire est _Stream<Item>_
  • Le type de retour est _List<Item>_

Voici le résultat du test à partir du code utilisé ci-dessous:

_-------- Without flatMap() -------------------------------
     collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]

-------- With flatMap() ----------------------------------
     collect() returns: [Laptop, Phone, Mouse, Keyboard]
_

Code utilisé :

_import Java.util.Arrays;
import Java.util.Collection;
import Java.util.List;
import Java.util.stream.Collectors;

public class Parcel {
  String name;
  List<String> items;

  public Parcel(String name, String... items) {
    this.name = name;
    this.items = Arrays.asList(items);
  }

  public List<String> getItems() {
    return items;
  }

  public static void main(String[] args) {
    Parcel Amazon = new Parcel("Amazon", "Laptop", "Phone");
    Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
    List<Parcel> parcels = Arrays.asList(Amazon, ebay);

    System.out.println("-------- Without flatMap() ---------------------------");
    List<List<String>> mapReturn = parcels.stream()
      .map(Parcel::getItems)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + mapReturn);

    System.out.println("\n-------- With flatMap() ------------------------------");
    List<String> flatMapReturn = parcels.stream()
      .map(Parcel::getItems)
      .flatMap(Collection::stream)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + flatMapReturn);
  }
}
_
75
nxhoaf

La fonction que vous passez à stream.map doit renvoyer un objet. Cela signifie que chaque objet du flux d'entrée génère exactement un objet dans le flux de sortie.

La fonction que vous passez à stream.flatMap renvoie un flux pour chaque objet. Cela signifie que la fonction peut renvoyer n'importe quel nombre d'objets pour chaque objet d'entrée (y compris aucun). Les flux résultants sont ensuite concaténés en un flux de sortie.

38
Philipp

pour une carte, nous avons une liste d'éléments et une (fonction, action) f donc:

[a,b,c] f(x) => [f(a),f(b),f(c)]

et pour la carte à plat, nous avons une liste d'éléments et une (fonction, action) f et nous voulons que le résultat soit aplati:

[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]

J'ai l'impression que la plupart des réponses ici compliquent trop le problème simple. Si vous comprenez déjà comment fonctionne la map, cela devrait être assez facile à comprendre.

Il existe des cas où nous pouvons nous retrouver avec des structures imbriquées indésirables en utilisant map(), la méthode flatMap() est conçue pour résoudre ce problème en évitant le wrapping.


Exemples:

1

List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .collect(Collectors.toList());

Nous pouvons éviter d'avoir des listes imbriquées en utilisant flatMap:

List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .flatMap(i -> i.stream())
  .collect(Collectors.toList());

2

Optional<Optional<String>> result = Optional.of(42)
      .map(id -> findById(id));

Optional<String> result = Optional.of(42)
      .flatMap(id -> findById(id));

où:

private Optional<String> findById(Integer id)
24
Grzegorz Piwowarek

L'article d'Oracle sur les options souligne cette différence entre map et flatmap:

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");

Malheureusement, ce code ne compile pas. Pourquoi? La variable computer étant de type Optional<Computer>, il est donc parfaitement correct d'appeler la méthode map. Cependant, getSoundcard () retourne un objet de type Optional. Cela signifie que le résultat de l'opération de mappage est un objet de type Optional<Optional<Soundcard>>. Par conséquent, l'appel de getUSB () n'est pas valide, car la plus externe, Optional, contient comme valeur un autre, qui, bien sûr, ne prend pas en charge la méthode getUSB ().

Avec les flux, la méthode flatMap prend une fonction en tant qu'argument, ce qui retourne un autre flux. Cette fonction est appliquée à chaque élément d'un flux, ce qui donnerait un flux de flux. Cependant, flatMap a pour effet de remplacer chaque flux généré par le contenu de ce flux. En d'autres termes, tous les flux distincts générés par la fonction sont fusionnés ou "aplatis" en un seul flux. Ce que nous voulons ici est quelque chose de similaire, mais nous voulons "aplatir" un optionnel à deux niveaux en un .

Facultatif prend également en charge une méthode flatMap. Son but est d'appliquer la fonction de transformation sur la valeur d'un optionnel (comme l'opération de mappage), puis d'aplatir le optionnel à deux niveaux obtenu en un seul .

Donc, pour que notre code soit correct, nous devons le réécrire comme suit en utilisant flatMap:

String version = computer.flatMap(Computer::getSoundcard)
                   .flatMap(Soundcard::getUSB)
                   .map(USB::getVersion)
                   .orElse("UNKNOWN");

Le premier flatMap garantit qu'un Optional<Soundcard> est renvoyé au lieu d'un Optional<Optional<Soundcard>>, et le deuxième flatMap a le même objectif que de renvoyer un Optional<USB>. Notez que le troisième appel doit simplement être une map () car getVersion () renvoie une chaîne plutôt qu'un objet facultatif.

http://www.Oracle.com/technetwork/articles/Java/java8-optional-2175753.html

19
Rusty Core

Je ne suis pas vraiment sûr d'être censé répondre à cette question, mais chaque fois que je fais face à une personne qui ne comprend pas cela, j'utilise le même exemple.

Imaginez que vous avez une pomme. Une map transforme ce Apple en Apple-juice par exemple ou en un mappage n à un.

Prenez le même Apple et n'en récupérez que les graines, c'est ce que flatMap fait, ou un n à plusieurs, un Apple en entrée, beaucoup de graines en sortie.

13
Eugene

Carte: - Cette méthode prend une fonction en argument et renvoie un nouveau flux constitué des résultats générés en appliquant la fonction transmise à tous les éléments du flux.

Imaginons que j’ai une liste de valeurs entières (1,2,3,4,5) et une interface de fonction dont la logique est au carré de l’entier passé. (e -> e * e).

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> newList = intList.stream().map( e -> e * e ).collect(Collectors.toList());

System.out.println(newList);

sortie:-

[1, 4, 9, 16, 25]

Comme vous pouvez le constater, une sortie est un nouveau flux dont les valeurs sont au carré des valeurs du flux d’entrée.

[1, 2, 3, 4, 5] -> apply e -> e * e -> [ 1*1, 2*2, 3*3, 4*4, 5*5 ] -> [1, 4, 9, 16, 25 ]

http://codedestine.com/Java-8-stream-map-method/

FlatMap: - Cette méthode prend une fonction en argument, cette fonction accepte un paramètre T en tant qu'argument d'entrée et renvoie un flux de paramètres R en tant que valeur de retour. Lorsque cette fonction est appliquée à chaque élément de ce flux, elle génère un flux de nouvelles valeurs. Tous les éléments de ces nouveaux flux générés par chaque élément sont ensuite copiés dans un nouveau flux, qui sera une valeur de retour de cette méthode.

Imaginons, j'ai une liste d'objets d'étudiant, où chaque étudiant peut choisir plusieurs matières.

List<Student> studentList = new ArrayList<Student>();

  studentList.add(new Student("Robert","5st grade", Arrays.asList(new String[]{"history","math","geography"})));
  studentList.add(new Student("Martin","8st grade", Arrays.asList(new String[]{"economics","biology"})));
  studentList.add(new Student("Robert","9st grade", Arrays.asList(new String[]{"science","math"})));

  Set<Student> courses = studentList.stream().flatMap( e -> e.getCourse().stream()).collect(Collectors.toSet());

  System.out.println(courses);

sortie:-

[economics, biology, geography, science, history, math]

Comme vous pouvez le constater, une sortie est un nouveau flux dont les valeurs sont une collection de tous les éléments des flux renvoyés par chaque élément du flux d’entrée.

[S1, S2, S3] -> {{"histoire", "maths", "géographie"}, {"économie", "biologie"}, {"sciences", "maths"}] -> prenez des sujets uniques - > [économie, biologie, géographie, sciences, histoire, mathématiques]

http://codedestine.com/Java-8-stream-flatmap-method/

11
lalitbhagtani

map () et flatMap ()

  1. map()

Prenons juste une fonction a param lambda où T est élément et R l’élément de retour construit en utilisant T. À la fin, nous aurons un flux avec des objets de type R. Un exemple simple peut être:

Stream
  .of(1,2,3,4,5)
  .map(myInt -> "preFix_"+myInt)
  .forEach(System.out::println);

Il prend simplement les éléments 1 à 5 du type Integer, utilise chaque élément pour construire un nouvel élément à partir du type String avec la valeur "prefix_"+integer_value et l'imprime.

  1. flatMap()

Il est utile de savoir que flapMap () prend la fonction F<T, R>

  • T est un type de à partir duquel un Stream peut être construit/avec. Ce peut être une liste (T.stream ()), un tableau (Arrays.stream (someArray)), etc., tout ce à partir duquel un flux peut être avec/ou une forme. dans l'exemple ci-dessous, chaque dev a plusieurs langues, donc dev. Langues est une liste et utilisera un paramètre lambda.

  • R est le flux résultant qui sera construit en utilisant T. Sachant que nous avons plusieurs instances de T, nous aurons naturellement plusieurs flux de R. Tous ces flux de type R seront maintenant combinés en un seul flux 'plat' de type R.

Exemple

Les exemples de Bachiri Taoufiq voir sa réponse ici sont simples et faciles à comprendre. Juste pour la clarté, disons simplement que nous avons une équipe de développeurs:

dev_team = {dev_1,dev_2,dev_3}

, chaque développeur connaissant plusieurs langues:

dev_1 = {lang_a,lang_b,lang_c},
dev_2 = {lang_d},
dev_2 = {lang_e,lang_f}

Appliquer Stream.map () sur dev_team pour obtenir les langues de chaque dev:

dev_team.map(dev -> dev.getLanguages())

vous donnera cette structure:

{ 
  {lang_a,lang_b,lang_c},
  {lang_d},
  {lang_e,lang_f}
}

qui est fondamentalement un List<List<Languages>> /Object[Languages[]]. Pas très joli, ni Java8-like !!

avec Stream.flatMap() vous pouvez "aplatir" les choses en prenant la structure ci-dessus
et le transforme en {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}, qui peut en principe être utilisé comme List<Languages>/Language[]/ect...

donc la fin de votre code serait plus logique comme ceci:

dev_team
   .stream()    /* {dev_1,dev_2,dev_3} */
   .map(dev -> dev.getLanguages()) /* {{lang_a,...,lang_c},{lang_d}{lang_e,lang_f}}} */
   .flatMap(languages ->  languages.stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
   .doWhateverWithYourNewStreamHere();

ou simplement:

dev_team
       .stream()    /* {dev_1,dev_2,dev_3} */
       .flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
       .doWhateverWithYourNewStreamHere();

Quand utiliser map () et flatMap () :

  • Utilisez map() lorsque chaque élément de type T de votre flux est censé être mappé/transformé en un élément single de type R. Le résultat est un mappage de type (1 élément de départ -> 1 élément d'extrémité) et le nouveau flux d'éléments de type R est renvoyé.

  • Utilisez flatMap() lorsque chaque élément de type T de votre flux est censé être mappé/transformé en un Collections d'éléments de type R. Le résultat est un mappage de type (1 élément de départ -> n éléments d'extrémité). Ces collections sont ensuite fusionnées (ou aplaties) dans un nouveau flux d'éléments de type R. Ceci est utile par exemple pour représenter imbriqué boucles .

Pre Java 8:

List<Foo> myFoos = new ArrayList<Foo>();
    for(Foo foo: myFoos){
        for(Bar bar:  foo.getMyBars()){
            System.out.println(bar.getMyName());
        }
    }

Message Java 8

myFoos
    .stream()
    .flat(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));
11
arthur

C'est très déroutant pour les débutants. La différence fondamentale est que map émet un élément pour chaque entrée de la liste et flatMap est fondamentalement une opération map + flatten. Pour être plus clair, utilisez flatMap lorsque vous avez besoin de plusieurs valeurs, par exemple, si vous attendez une boucle pour renvoyer des tableaux, flatMap sera très utile dans ce cas.

J'ai écrit un blog à ce sujet, vous pouvez le vérifier ici .

1
Niraj Chauhan

Réponse simple.

L'opération map peut produire une Stream sur Stream. EX Stream<Stream<Integer>>

L'opération flatMap ne produira que Stream de quelque chose. EX Stream<Integer>

1
Melad Basilius

Une bonne analogie peut également être établie avec C # si vous le connaissez bien. Fondamentalement C # Select semblable à Java map et C # SelectMany Java flatMap. Il en va de même pour Kotlin pour les collections.

1
Arsenius

Les opérations de flux flatMap et map acceptent une fonction en entrée.

flatMap s'attend à ce que la fonction renvoie un nouveau flux pour chaque élément du flux et renvoie un flux qui regroupe tous les éléments des flux renvoyés par la fonction pour chaque élément. En d'autres termes, avec flatMap, pour chaque élément de la source, plusieurs éléments seront créés par la fonction. http://www.zoftino.com/Java-stream-examples#flatmap-operation

map s'attend à ce que la fonction retourne une valeur transformée et renvoie un nouveau flux contenant les éléments transformés. En d'autres termes, avec map, pour chaque élément de la source, un élément transformé sera créé par la fonction. http://www.zoftino.com/Java-stream-examples#map-operation

0
Arnav Rao

flatMap() tire également parti de l'évaluation partielle paresseuse des flux. Il lira le premier flux et, au besoin, ira au prochain flux. Le comportement est expliqué en détail ici: Est-ce que flatMap est garanti pour être paresseux?

0
S.K.