首页 文章

Java 8中map和flatMap方法的区别是什么?

提问于
浏览
468

在Java 8中,Stream.mapStream.flatMap方法之间有什么区别?

17 回答

  • 8

    mapflatMap 都可以应用于 Stream<T> 并且它们都返回 Stream<R> . 不同之处在于 map 操作为每个输入值生成一个输出值,而 flatMap 操作为每个输入值生成任意数量(零个或多个)值 .

    这反映在每个操作的参数中 .

    map 操作采用 Function ,为输入流中的每个值调用它并生成一个结果值,该值将发送到输出流 .

    flatMap 操作采用一个概念上想要消耗一个值并产生任意数量的值的函数 . 但是,在Java中,返回任意数量的值的方法很麻烦,因为方法只能返回零个或一个值 . 可以想象一个API,其中 flatMap 的映射器函数获取一个值并返回一个数组或 List 的值,然后将其发送到输出 . 鉴于这是流库,一种表示任意数量的返回值的特别方法是mapper函数本身返回一个流!映射器返回的流中的值将从流中排出并传递到输出流 . 每个调用mapper函数返回的"clumps"值在输出流中根本没有区分,因此输出据说是"flattened."

    典型的用途是 flatMap 的映射器函数如果要发送零值则返回 Stream.empty() ,如果想要返回多个值,则返回 Stream.of(a, b, c) . 但是当然可以返回任何流 .

  • 584

    Stream.flatMap ,因为它的名字可以猜到,是 mapflat 操作的组合 . 这意味着您首先将一个函数应用于您的元素,然后将其展平 . Stream.map 仅在不对流进行展平的情况下将函数应用于流 .

    要了解流整合的内容,请考虑像 [ [1,2,3],[4,5,6],[7,8,9] ] 这样的结构,它具有"two levels" . 展平这意味着将其转换为"one level"结构: [ 1,2,3,4,5,6,7,8,9 ] .

  • 37

    我想举两个例子来获得更实际的观点:
    第一个使用 Map 的例子:

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

    在第一个示例中没有任何特殊内容, Function 用于以大写形式返回 String .

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

    在第二个示例中,传递了List of List . It is NOT a Stream of Integer!
    If a transformation Function has to be used (through map), then first the Stream has to be flattened to something else (a Stream of Integer).
    如果删除flatMap,则返回以下错误: The operator + is undefined for the argument type(s) List, int.
    无法在整数列表中应用1!

  • 1

    Map: - 此方法将一个Function作为参数,并返回一个新流,该流包含通过将传递的函数应用于流的所有元素而生成的结果 .

    让我们假设,我有一个整数值列表(1,2,3,4,5)和一个函数接口,其逻辑是传递的整数的平方 . (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);
    

    输出: -

    [1, 4, 9, 16, 25]
    

    如您所见,输出是一个新流,其值是输入流的值的平方 .

    [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: - 此方法将一个Function作为参数,此函数接受一个参数T作为输入参数,并返回一个参数R流作为返回值 . 当此函数应用于此流的每个元素时,它会生成一个新值流 . 然后将每个元素生成的这些新流的所有元素复制到新流中,该流将是此方法的返回值 .

    我们的图像,我有一个学生对象列表,每个学生可以选择多个科目 .

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

    输出: -

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

    如您所见,输出是一个新流,其值是输入流的每个元素返回的流的所有元素的集合 .

    [S1,S2,S3] - > [{“历史”,“数学”,“地理”},{“经济学”,“生物学”},{“科学”,“数学”}] - >采取独特的科目 - > [经济学,生物学,地理学,科学,历史,数学]

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

  • 99

    Please go through the post fully to get a clear idea, map vs flatMap: 要从列表中返回每个单词的长度,我们会执行以下操作 .

    For example:-
    考虑一个列表 [“STACK”, ”OOOVVVER”] ,我们试图返回一个像 [“STACKOVER”] 这样的列表(只返回该列表中的唯一字母)最初,我们会做类似下面的事情从 [“STACK”, ”OOOVVVER”] 返回一个列表 [“STACKOVER”]

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

    这里的问题是,Lambda传递给map方法为每个单词返回一个String数组,所以map方法返回的流实际上是Stream类型,但我们需要的是Stream来表示一个字符流,下面的图片说明了问题 .

    Figure A:

    enter image description here

    您可能会认为,我们可以使用flatmap解决此问题,
    好的,让我们看看如何通过使用map和Arrays.stream来解决这个问题 . 首先,你需要一个字符流而不是数组流 . 有一个名为Arrays.stream()的方法可以获取一个数组并生成一个流,例如:

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

    上面仍然不起作用,因为我们现在最终得到一个流列表(更确切地说,Stream>),相反,我们必须首先将每个单词转换为单个字母的数组,然后将每个数组转换为单独的流

    By using flatMap we should be able to fix this problem as below:

    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将执行不是使用流而是使用该流的内容映射每个数组 . 使用map(Arrays :: stream)时生成的所有单个流都会合并到一个流中 . 图B说明了使用flatMap方法的效果 . 将它与图A中的 Map 进行比较. Figure B
    enter image description here

    The flatMap method lets you replace each value of a stream with another stream and then joins all the generated streams into a single stream.

  • 22

    传递给 stream.map 的函数必须返回一个对象 . 这意味着输入流中的每个对象都会在输出流中生成一个对象 .

    传递给 stream.flatMap 的函数返回每个对象的流 . 这意味着该函数可以为每个输入对象返回任意数量的对象(包括无) . 然后将得到的流连接到一个输出流 .

  • 0

    One line answer: flatMap helps to flatten a Collection<Collection<T>> into a Collection<T> . 以同样的方式,它也会将 Optional<Optional<T>> 压平为 Optional<T> .

    enter image description here

    如您所见,仅使用 map()

    • 中间类型是 Stream<List<Item>>

    • 返回类型是 List<List<Item>>

    flatMap()

    • 中间类型是 Stream<Item>

    • 返回类型是 List<Item>

    这是下面使用的代码中的 test result

    -------- Without flatMap() -------------------------------
         collect return: [[Laptop, Phone], [Mouse, Keyboard]]
    
    -------- With flatMap() ----------------------------------
         collect return: [Laptop, Phone, Mouse, Keyboard]
    

    Code used

    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 return: " + 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 return: " + flatMapReturn);
      }
    }
    
  • 0

    对于Map,我们有一个元素列表和一个(函数,动作)f,所以:

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

    对于平面 Map ,我们有一个元素列表列表,我们有一个(函数,动作)f,我们希望结果被展平:

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

    我有一种感觉,这里的大多数答案都使这个简单问题复杂化 . 如果您已经了解 map 的工作原理应该相当容易掌握 .

    在使用 map() 时,有些情况下我们可能会遇到不需要的嵌套结构, flatMap() 方法旨在通过避免包装来克服这个问题 .


    例子:

    1

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

    我们可以通过使用 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));
    

    哪里:

    private Optional<String> findById(Integer id)
    
  • 10

    Oracle关于Optional的文章强调了map和flatmap之间的区别:

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

    不幸的是,这段代码无法编译 . 为什么?变量计算机的类型为Optional <Computer>,因此调用map方法是完全正确的 . 但是,getSoundcard()返回Optional类型的对象 . 这意味着map操作的结果是Optional <Optional <Soundcard >>类型的对象 . 因此,对getUSB()的调用无效,因为最外面的Optional包含另一个Optional作为其值,当然这不支持getUSB()方法 . 对于流,flatMap方法将函数作为参数,返回另一个流 . 此函数应用于流的每个元素,这将导致流的流 . 但是,flatMap具有用该流的内容替换每个生成的流的效果 . 换句话说,由函数生成的所有单独的流被合并或“展平”为单个流 . 我们在这里想要的是类似的东西,但我们想要将两级可选“扁平化”为一个 . 可选还支持flatMap方法 . 它的目的是将变换函数应用于Optional的值(就像map操作一样),然后将生成的两级Optional压缩为一个 . 因此,为了使我们的代码更正,我们需要使用flatMap重写如下:

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

    第一个flatMap确保返回Optional <Soundcard>而不是Optional <Optional <Soundcard >>,第二个flatMap实现返回Optional <USB>的相同目的 . 请注意,第三个调用只需要是map(),因为getVersion()返回String而不是Optional对象 .

    http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

  • 182

    我不太确定我应该回答这个问题,但每当我遇到一个不理解这一点的人时,我都会使用相同的例子 .

    想象一下,你有一个苹果 . 例如, map 正在将该苹果转换为 apple-juice 或一对一映射 .

    拿同一个苹果,只取出它的种子,这就是 flatMap 做的,或者一对多,一个苹果作为输入,许多种子作为输出 .

  • 22

    map() and flatMap()

    • map()

    只需要一个函数一个lambda参数,其中T是元素,R是使用T构建的返回元素 . 最后我们将得到一个带有类型R对象的Stream . 一个简单的例子可以是:

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

    它只需要类型 Integer 的元素1到5,使用每个元素从类型 String 构建一个值为 "prefix_"+integer_value 的新元素并将其打印出来 .

    • flatMap()

    知道flapMap()采用函数 F<T, R> 在哪里很有用

    • T是可以从中构建Stream的类型 . 它可以是List(T.stream()),数组(Arrays.stream(someArray))等 . 来自的任何东西流可以与/或形成 . 在下面的例子中,每个开发人员都有很多语言,所以dev . 语言是一个List,将使用lambda参数 .

    • R是将使用T构建的结果Stream . 知道我们有很多T实例,我们自然会有很多来自R的Streams . 来自Type R的所有这些Streges将 now be combined into one 单个'flat'来自Type R的Stream .

    Example

    Bachiri Taoufiq see its answer here的例子简单易懂 . 为了清楚起见,我们假设我们有一个开发团队:

    dev_team = {dev_1,dev_2,dev_3}
    

    ,每个开发人员都知道多种语言:

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

    在dev_team上应用Stream.map()以获取每个开发人员的语言:

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

    会给你这个结构:

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

    这基本上是 List<List<Languages>> /Object[Languages[]] . 不是很漂亮,也不像Java8!

    使用 Stream.flatMap() ,您可以'flatten'因为它采用上述结构
    并将其转换为 {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f} ,基本上可以用作 List<Languages>/Language[]/ect ...

    所以你的代码结束会更有意义:

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

    或者干脆:

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

    When to use map() and use flatMap()

    • 当流中的T类型的每个元素都被映射/转换为R类型的单个元素时,使用 map() . 结果是类型(1个开始元素 - > 1个结束元素)和新的元素流的映射 . 返回类型R.

    • 当您的流中的T类型的每个元素都应该映射/转换为R类型的元素集合时,使用 flatMap() . 结果是类型的映射(1个起始元素 - > n个结束元素) . 然后将这些集合合并(或展平)为R类型的新元素流 . 例如,这可用于表示 nested loops .

    前Java 8:

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

    发布Java 8

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

    如果您熟悉,也可以使用C#进行类比 . 基本上C# Select 类似于java map 和C# SelectMany java flatMap . 同样适用于Kotlin的收藏品 .

  • 17

    这对初学者来说非常困惑 . 基本区别是 map 为列表中的每个条目发出一个项目, flatMap 基本上是 map flatten 操作 . 更清楚的是,当需要多个值时使用flatMap,例如,当您期望循环返回数组时,flatMap在这种情况下将非常有用 .

    我写了一篇关于此的博客,你可以查看here .

  • 1

    流操作 flatMapmap 接受函数作为输入 .

    flatMap 期望函数为流的每个元素返回一个新流,并返回一个流,该流组合了每个元素的函数返回的流的所有元素 . 换句话说,对于 flatMap ,对于源中的每个元素,函数将创建多个元素 . http://www.zoftino.com/java-stream-examples#flatmap-operation

    map 期望函数返回转换后的值并返回包含已转换元素的新流 . 换句话说,对于 map ,对于源中的每个元素,函数将创建一个变换元素 . http://www.zoftino.com/java-stream-examples#map-operation

  • 25

    flatMap() 也利用了对流的部分惰性评估 . 它将读取第一个流,只有在需要时才会进入下一个流 . 这里详细解释了这种行为:Is flatMap guaranteed to be lazy?

  • 7

    假设我们有:

    List<List<Integer>> lists = List.of(List.of(1, 2), List.of(3, 4));
    

    flatMap

    让我们接受一个接受一个参数的 Function 并产生一个 Stream 的值:

    Function<List<Integer>, Stream<Integer>> f = list -> list.stream();
    

    并将此函数应用于每个列表:

    for (List<Integer> list : lists) f.apply(list).forEach(System.out::print);
    

    这正是Stream.flatMap可以做的:

    lists.stream().flatMap(list -> list.stream()).forEach(System.out::print);
    

    Map

    让我们接受一个接受一个参数并产生一个值的 Function

    Function<List<Integer>, String> f = list -> list.toString();
    

    并将此函数应用于每个列表:

    for (List<Integer> list : lists) System.out.print(f.apply(list));
    

    这正是Stream.map可以做的:

    lists.stream().map(list -> list.toString()).forEach(System.out::print);
    

相关问题