在Java8中map()和flatMap()方法有什么区别?

在Java8中,Stream.map()Stream.flatMap()方法有什么区别?

570502 次浏览

Stream.flatMap,顾名思义,是mapflat操作的组合。这意味着您首先将函数应用于元素,然后将其展平。Stream.map只将函数应用于流而不展平流。

要理解流的扁平化包含什么,请考虑像[ [1,2,3],[4,5,6],[7,8,9] ]这样具有“两个级别”的结构。扁平化这意味着将其转换为“一个级别”结构:[ 1,2,3,4,5,6,7,8,9 ]

传递给stream.map的函数必须返回一个对象。这意味着输入流中的每个对象在输出流中只导致一个对象。

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

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

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

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

flatMap操作需要一个概念上想要消耗一个值并产生任意数量的值的函数。然而,在Java,方法返回任意数量的值是很麻烦的,因为方法只能返回零或一个值。人们可以想象一个API,其中flatMap的映射器函数接受一个值并返回一个数组或List的值,然后将其发送到输出。鉴于这是流库,表示任意数量的返回值的一种特别合适的方法是映射器函数本身返回一个流!映射器返回的流中的值从流中排出并传递到输出流。每次调用映射器函数返回的值的“团块”在输出流中根本没有区分,因此输出被称为“扁平化”。

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

我想举两个例子来得到一个更多实用的观点:
使用map的第一个示例:

@Testpublic 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的第二个示例:

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

在第二个例子中,传递了一个列表流。它不是整数流!
如果必须使用转换函数(通过map),那么首先必须将Stream展平为其他东西(整数流)。
如果删除flatMap,则返回以下错误:对于参数类型List, int,运算符+未定义。
在整数的List上应用+1是不可能的!

请仔细阅读这篇文章,以获得一个清晰的想法,

地图vs平面地图:

要从列表中返回每个单词的长度,我们会做如下操作…

下面给出的简短版本

当我们收集两个列表时,如下所示

没有平面map=>[1,2],[1,1]=>[[1,2],[1,1]]这里有两个列表放在一个列表中,所以输出将是包含列表的列表

随着平面map=>[1,2],[1,1]=>[1,2,1,1]这里两个列表被扁平化,只有值被放置在列表中,所以输出将是只包含元素的列表

基本上它把所有的物体合并成一个

##详细版本如下:-

例如:-
考虑一个列表["STACK","OOVVVER"],我们试图返回一个类似于["堆栈"]的列表(仅返回该列表中的唯一字母)最初,我们会做如下操作,从["STACK","OOVVVER"]返回一个列表["堆栈"]

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类型,但是我们需要的是Stream来表示一个字符流,下图说明了问题。

图A:

在此处输入图片描述

你可能会想,我们可以使用平面图来解决这个问题,
好吧,让我们看看如何使用地图来解决这个问题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>),相反,我们必须首先将每个单词转换为单个字母的数组,然后将每个数组转换为单独的流

通过使用platMap,我们应该能够解决这个问题,如下所示:

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

platMap将执行映射每个数组而不是使用stream,而是使用该流的内容。使用map(Array::stream)时生成的所有单个流都合并到一个流中。图B说明了使用flatMap方法的效果。将其与图A中的map进行比较。图b输入图片描述

通过flatMap方法,您可以将流的每个值替换为另一个流,然后将所有生成的流连接到单个流中。

地图:-此方法将一个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:-该方法接受一个函数作为参数,该函数接受一个参数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/

Oracle关于可选的文章强调了map和platmap之间的区别:

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

不幸的是,这段代码无法编译。为什么?变量计算机是Optional<Computer>类型,因此调用map方法。但是,getSoundCard()返回类型为可选。这意味着map操作的结果是类型Optional<Optional<Soundcard>>的对象。因此,对getUSB()是无效的,因为最外面的可选包含作为其值另一个可选的,当然不支持getUSB()方法。

对于流,platMap方法接受一个函数作为参数,它返回另一个流。此函数应用于每个元素一条溪流,这将导致一条溪流。然而,platMap具有将每个生成的流替换为流的内容。换句话说,所有独立的流由函数合并或“扁平化”成一个生成我们在这里想要的是类似的东西,但是我们想要"flatten"一个两个级别的可选为一个。

可选还支持flamMap方法。它的目的是应用对可选值的转换函数(就像地图一样操作做),然后将生成的两级可选展平为

一个人

因此,为了使我们的代码正确,我们需要重写如下

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

第一个平面映射确保返回Optional<Soundcard>而不是Optional<Optional<Soundcard>>,并且第二个平面映射实现相同的目的来返回Optional<USB>。请注意第三个调用只需要是一个map(),因为getVersion()返回一个字符串而不是可选对象。

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

如果您熟悉C#,也可以使用C#进行很好的类比。基本上,C#Select类似于javamap和C#SelectMany javaflatMap。同样适用于集合的静态编程语言。

我有一种感觉,这里的大多数答案都将简单的问题复杂化了。如果你已经理解了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)

对于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)]

这对初学者来说是非常混乱的。基本区别是map为列表中的每个条目发出一个项目,而flatMap基本上是map+flatten操作。更清楚地说,当你需要多个值时,使用flatMap,例如,当你期望一个循环返回数组时,flatMap在这种情况下真的很有帮助。

我写了一篇关于这个的博客,你可以看看这里

map()和platMap()

  1. 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的新元素并将其打印出来。

  1. flatMap()

这是有用的要知道,flatMap()需要一个函数F<T, R>在哪里

  • T是来自可以从哪个Stream构建/使用哪个Stream的类型。它可以是List(T.stream())、数组(Arrays.stream(一些数组))等…任何可以与Stream一起/或形成的东西。在下面的示例中,每个开发人员都有许多语言,因此开发语言是一个List,将使用一个lambda参数。

  • R是将使用T构建的结果Stream。知道我们有很多T的实例,我们自然会有很多来自R的Streams。来自R型的所有这些Streams将现在合并成一个来自R型的单个“平面”Stream。

示例

Bachiri Taoufiq的例子[见这里的答案]1简单易懂。为了清楚起见,假设我们有一个开发团队:

dev_team = {dev_1,dev_2,dev_3}

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

dev_1 = {lang_a,lang_b,lang_c},dev_2 = {lang_d},dev_3 = {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(),您可以“扁平化”事物,因为它采用上述结构
并将其转换为{lang_a, lang_b, lang_c, lang_d, lang_e, lang_f},基本上可以用作List<Languages>/Language[]/etc

所以最后,你的代码会像这样更有意义:

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

什么时候使用map()和ping Map()

  • 当流中T类型的每个元素都应该映射/转换为R类型的单一元素时,使用map()。结果是类型(1个起始元素->1个结束元素)的映射,并返回R类型的新元素流。

  • 当流中的每个T类型元素都应该映射/转换为R类型元素的收藏时,使用flatMap()。结果是类型(1个开始元素->n个结束元素)的映射。然后,这些集合合并(或扁平化)到R类型元素的新流。这对于表示嵌套循环很有用。

Java8:

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

Java8

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

流操作flatMapmap接受一个函数作为输入。

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

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

一行回答:#0有助于将#1展平为#2.以同样的方式,它也会将Optional<Optional<T>>压平为Optional<T>

在此处输入图片描述

如你所见,只有map()

  • 中间类型是Stream<List<Item>>
  • 返回类型为List<List<Item>>

flatMap()

  • 中间类型是Stream<Item>
  • 返回类型为List<Item>

这是下面使用的代码中的测试结果

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

使用的代码

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

我不太确定我应该回答这个问题,但每次我面对不理解这个问题的人时,我都会用同样的例子。

假设你有一个苹果。例如,map正在将该苹果转换为apple-juice一对一映射。

拿同一个苹果,只从里面得到种子,这就是flatMap所做的,或者一对多,一个苹果作为输入,许多种子作为输出。

flatMap()还利用了流的部分惰性评估。它将读取第一个流,只有在需要时才会转到下一个流。这种行为在这里详细解释:就一定会是懒的吗?

简单的回答。

map操作可以产生StreamStream.EXStream<Stream<Integer>>

flatMap操作只会产生Stream的东西。EXStream<Integer>

如果你认为map()是一个迭代(一个级别for循环),flatmap()是一个两级迭代(就像嵌套的for循环)。(输入每个迭代元素foo,然后执行foo.getBarList()并再次迭代barList


map():取一个流,对每个元素做一些事情,收集每个过程的单个结果,输出另一个流。“做某事函数”的定义是隐式的。如果任何元素的处理结果为null,则使用null来组成最终的流。因此,结果流中的元素数量将等于进审量流。

flatmap():取一个元素/流的流和一个函数(显式定义),将函数应用于每个流的每个元素,并收集所有中间结果流为更大的流(“展平”)。如果任何元素的处理结果为null,则为“展平”的最后一步提供空流。结果流中的元素数量,是所有输入中所有参与元素的总和,如果输入是几个流。

. map用于#0映射

Stream.of("dog", "cat")              // stream of 2 Strings.map(s -> s.length())            // stream of 2 Integers: [3, 3]

它将任何项目A转换为任何项目BJavadoc


. flamMap用于#0连接

Stream.of("dog", "cat")             // stream of 2 Strings.flatMapToInt(s -> s.chars())   // stream of 6 ints:      [d, o, g, c, a, t]

它--1将任何项目A转换为Stream< B>,然后--2将所有流连接成一个(平面)流。Javadoc


注1:尽管后一个示例平坦到原语流(IntStream)而不是对象流(Stream),但它仍然说明了.flatMap的想法。

注2:尽管有名称,String.chars()方法返回整数。所以实际的集合将是:[100, 111, 103, 99, 97, 116],其中100'd'的代码,111'o'等的代码。同样,出于说明目的,它被表示为[d, o, g, c, a, t]。

通过阅读所有消息,理解的简单方法是:

  • 如果您有flat元素列表,请使用map:[0,1,2,3,4,5]
  • 如果您有一个元素列表:[[1,3,5],[2,4,6]],请使用flatMap。这意味着,您的列表需要是扁平化才能将地图操作应用于每个元素

map()获取一个Stream并将其转换为另一个Stream。它在Stream的每个元素上应用一个函数,并将返回值存储到新的Stream中。它不会展平流。但是flattMap()是map和平面操作的组合,即它将函数应用于元素并展平它们。2)map()仅用于转换,但flattMap()用于转换和展平。请阅读更多这里。https://javaint4bytes.blogspot.com/2022/11/stream-flatmap-in-java-with-examples.html