在Java 8中,Stream.map
和Stream.flatMap
方法之间的区别是什么?
答案 0 :(得分:668)
map
和flatMap
都可以应用于Stream<T>
,并且它们都返回Stream<R>
。不同之处在于map
操作为每个输入值生成一个输出值,而flatMap
操作为每个输入值生成任意数量(零个或多个)值。
这反映在每个操作的参数中。
map
操作采用Function
,为输入流中的每个值调用该值,并生成一个结果值,该值将发送到输出流。
flatMap
操作采用一个概念上想要消耗一个值并产生任意数量值的函数。然而,在Java中,返回任意数量的值的方法很麻烦,因为方法只能返回零或一个值。可以想象一个API,其中flatMap
的映射器函数接受一个值并返回一个数组或List
的值,然后将其发送到输出。鉴于这是流库,一种表示任意数量的返回值的特别方法是mapper函数本身返回一个流!映射器返回的流中的值将从流中排出并传递到输出流。 &#34; clumps&#34;每次调用mapper函数返回的值都不会在输出流中被区分,因此输出被称为“#34;
flatMap
的映射器函数的典型用途是,如果要发送零值,则返回Stream.empty()
;如果要返回多个值,则返回Stream.of(a, b, c)
之类的值{{1}}。但是当然可以返回任何流。
答案 1 :(得分:333)
Stream.flatMap
,因为它的名称可以猜到,是map
和flat
操作的组合。这意味着您首先将一个函数应用于元素,然后将其展平。 Stream.map
仅在不对流进行展平的情况下将函数应用于流。
要了解流包含的展平,请考虑[ [1,2,3],[4,5,6],[7,8,9] ]
这样的结构,它具有“两个级别”。展平这意味着将其转换为“一级”结构:[ 1,2,3,4,5,6,7,8,9 ]
。
答案 2 :(得分:202)
我想举两个例子来获得更多实际观点:
第一个使用地图的例子:
@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。 它不是整数流!
如果必须使用转换函数(通过map),则首先必须将Stream展平为其他内容(整数流)。
如果删除了flatMap,则返回以下错误:对于参数类型List,int,,运算符+未定义。
无法在整数列表中应用+ 1!
答案 3 :(得分:119)
请完整阅读帖子以获得明确的想法,
map vs flatMap:
要从列表中返回每个单词的长度,我们会执行以下操作。
当我们收集两个列表时,如下所示
没有 平面地图 =&gt; [1,2],[1,1] =&gt; [[1,2],[1,1]] 这里有两个列表放在列表中,因此输出将是包含列表的列表
使用 平面地图 =&gt; [1,2],[1,1] =&gt; [1,2,1,1] 这里有两个列表被展平,只有值放在列表中,因此输出将是仅包含元素的列表
基本上它将所有对象合并为一个
##详细版本如下: -
例如: -
考虑列表 [“STACK”,“OOOVVVER”] ,我们正在尝试返回 [ “STACKOVER”] (仅返回该列表中的唯一字母)
最初,我们会执行以下操作,从 [“STACK”,“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());
}
}
这里的问题是,传递给map方法的Lambda为每个单词返回一个String数组,所以map方法返回的流实际上是Stream类型,但我们需要的是Stream来表示一个字符流,下面图像说明了问题。
图A:
您可能会认为,我们可以使用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&gt;),相反,我们必须首先将每个单词转换为单个字母的数组,然后将每个数组分成单独的流
通过使用flatMap,我们应该可以解决此问题,如下所示:
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中的地图进行比较。 图B
flatMap方法允许您用另一个流替换流的每个值,然后将所有生成的流连接到一个流中。
答案 4 :(得分:63)
单行答案:flatMap
有助于将Collection<Collection<T>>
简化为Collection<T>
。同样,它也会将 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);
}
}
答案 5 :(得分:38)
传递给stream.map
的函数必须返回一个对象。这意味着输入流中的每个对象都会在输出流中生成一个对象。
传递给stream.flatMap
的函数会返回每个对象的流。这意味着该函数可以为每个输入对象返回任意数量的对象(包括无)。然后将得到的流连接到一个输出流。
答案 6 :(得分:25)
对于Map我们有一个元素列表和一个(函数,动作)f so:
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.annotation</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.metatype.annotations</artifactId>
</dependency>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>com.aem.site.aem-site</Bundle-SymbolicName>
<Sling-Model-Packages>
com.aem.sites.models
</Sling-Model-Packages>
<Import-Package>javax.inject;version=0.0.0,*</Import-Package>
</instructions>
</configuration>
</plugin>
对于平面地图,我们有一个元素列表列表,我们有一个(函数,动作)f,我们希望将结果展平:
[a,b,c] f(x) => [f(a),f(b),f(c)]
答案 7 :(得分:23)
我有一种感觉,这里的大多数答案都使这个简单问题复杂化。如果你已经理解了map
的工作原理应该很容易理解。
在使用map()
时,我们可能会遇到不需要的嵌套结构,flatMap()
方法旨在通过避免包装来克服此问题。
示例:
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());
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)
答案 8 :(得分:22)
A -> B
映射Stream.of("dog", "cat") // stream of 2 Strings
.map(s -> s.length()) // stream of 2 Integers: [3, 3]
它将任何项A
转换为任何项B
。 Javadoc
A -> Stream< B>
的补充Stream.of("dog", "cat") // stream of 2 Strings
.flatMapToInt(s -> s.chars()) // stream of 6 ints: [d, o, g, c, a, t]
it --- 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]。
答案 9 :(得分:18)
Oracle关于“可选”的文章强调了地图和平面地图之间的区别:
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
不幸的是,这段代码没有编译。为什么?变量计算机 属于
Optional<Computer>
类型,所以调用它是完全正确的 地图方法。但是,getSoundcard()返回一个类型的对象 可选的。这意味着地图操作的结果是Optional<Optional<Soundcard>>
类型的对象。结果,打电话给 getUSB()无效,因为最外面的Optional包含as 值另一个可选,当然不支持getUSB() 方法使用stream,flatMap方法将函数作为参数, 返回另一个流。此功能适用于每个元素 流的流,这将导致流的流。然而, flatMap具有通过替换每个生成的流的效果 该流的内容。换句话说,所有单独的流 由函数得到合并或“扁平化”生成的#34;合而为一 单流。我们想要的是类似的东西,但我们想要的 &#34;平坦化&#34;一个两级可选的。
可选还支持flatMap方法。其目的是申请 可选值的转换函数(就像地图一样) 操作确实)然后将生成的两级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()返回一个 字符串而不是可选对象。
http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
答案 10 :(得分:11)
我不确定我应该回答这个问题,但是每次遇到不了解此问题的人时,我都会使用相同的示例。
想象你有一个苹果。例如,map
正在将该苹果转换为apple-juice
或一对一映射。
拿起同一个苹果,只取出其中的种子,这就是flatMap
的作用,或者是一对多,一个苹果作为输入,许多种子作为输出。
答案 11 :(得分:10)
map()和flatMap()
map()
只需要一个函数一个lambda参数,其中T是元素,R是使用T构建的返回元素。最后,我们将使用一个类型为R的对象。一个简单的例子可以是:
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))等。来自Stream可以与/或形成的任何东西。在下面的例子中,每个开发人员都有很多语言,所以dev。语言是一个List,将使用lambda参数。
R是将使用T构建的结果Stream。知道我们有很多T实例,我们自然会有很多来自R的Streams。来自Type R的所有这些Strends现在将组合成一个单个&#39; flat&#39;来自类型R的流
示例强>
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()
你可以“变平”&#39;因为它需要上述结构
并将其转换为{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();
何时使用map()并使用flatMap():
当您的流中的T类型的每个元素都应该映射/转换为R类型的单个元素时,请使用map()
。结果是类型的映射(1个开始元素 - > 1个结束元素)并返回R类型的新元素流。
当您的流中的T类型的每个元素都应该映射/转换为R类型的元素的 Collections 时,请使用flatMap()
。结果是类型的映射(1个开始元素 - > n个结束元素)。然后,这些集合合并(或展平)到R类型的新元素流。这可用于表示嵌套循环。
Pre 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()));
答案 12 :(得分:10)
地图: - 此方法将一个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] - &gt; [{“history”,“math”,“geography”},{“economics”,“biology”},{“science”,“math”}] - &gt;采取独特的主题 - &gt; [经济学,生物学,地理学,科学,历史,数学]
答案 13 :(得分:1)
如果您熟悉,也可以使用C#进行类比。基本上C#Select
类似于java map
和C#SelectMany
java flatMap
。同样适用于Kotlin的收藏品。
答案 14 :(得分:1)
这对初学者来说非常困惑。基本区别为map
为列表中的每个条目发出一个项目,flatMap
基本上是map
+ flatten
操作。为了更清楚,当你需要多个值时使用flatMap,例如当你期望循环返回数组时,flatMap在这种情况下会非常有用。
我写了一篇关于此的博客,你可以查看here。
答案 15 :(得分:1)
简单的答案。
map
操作可以产生Stream
的{{1}}。EX Stream
Stream<Stream<Integer>>
操作只会产生flatMap
的东西。 EX Stream
答案 16 :(得分:0)
流操作flatMap
和map
接受一个函数作为输入。
flatMap
期望函数为该流的每个元素返回一个新流,并返回一个流,该流合并了该函数为每个元素返回的流的所有元素。换句话说,对于flatMap
,对于源中的每个元素,该函数将创建多个元素。 http://www.zoftino.com/java-stream-examples#flatmap-operation
map
期望函数返回转换后的值,并返回包含转换后元素的新流。换句话说,对于map
,对于源中的每个元素,该函数将创建一个转换后的元素。
http://www.zoftino.com/java-stream-examples#map-operation
答案 17 :(得分:0)
flatMap()
还利用了流的部分惰性评估。它将读取第一个流,并且仅在需要时才进入下一个流。该行为在此处进行了详细说明:Is flatMap guaranteed to be lazy?
答案 18 :(得分:0)
如果您将map()
视为迭代(一级for
循环),则flatmap()
是两级迭代(如嵌套的for
循环)。 (输入每个迭代元素foo
,然后进行foo.getBarList()
并在该barList
中再次迭代)
map()
:获取一个流,对每个元素进行处理,收集每个进程的单个结果,输出另一个流。 “执行某项功能”的定义是隐式的。如果任何元素的处理结果为null
,则使用null
组成最终流。因此,结果流中的元素数量将等于输入流的数量。
flatmap()
:获取一个 elements / streams 流和一个函数(显式定义),将该函数应用于每个流的每个元素,并将所有中间结果流收集为更大的流(“展平”)。如果任何元素的处理结果为null
,则将空流提供给“扁平化”的最后步骤。如果输入是多个流,则结果流中元素的数量是所有输入中所有参与元素的总数。
答案 19 :(得分:-2)
map()
由将给定功能应用于此流的元素的结果组成的流。 Map接受一个输入,该输入描述了如何将值转换为值。 假设我们想获取名为Saurabh的Student的年龄,到现在为止,我们仅从流中检索了完整的对象,但是我们该如何做呢? 我们可以使用map()将学生流转换为年龄流,如下所示。
int age = students.stream()
.filter(student -> SAURABH.equals(student.getName()))
.map(Student::getAge)
.findAny()
.orElse(0);
System.out.printf("*** Age of %s is %d\n",SAURABH, age);
现在让我们尝试在collect()的帮助下获取所有学生的姓名
Set<String> names = students.stream()
.map(Student::getName) // this will convert the Student Stream into String Stream by
// applying the getName()
.collect(Collectors.toSet());
System.out.printf("*** All the names from the list is %s\n",names);
map()vs flatMap()
假设我们想在学生列表中获得所有课程,那么我们可以编写如下代码:
Set<String> courses = students.stream()
.map(Student::getCourses)
.collect(Collectors.toSet())
**这里我们将出现如下编译错误
类型不匹配:无法从Set转换为Set 要解决此问题,我们使用flatMap()**
Java 8中的flatMap()
它返回一个流,该流包括将流中的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容而得到的结果。 flatMap将把流转换成简单流。在下面的示例中,我们使用flatMap将Stream数组转换为String流。
Set<String> courses = students.stream()
.map(Student::getCourses)
.flatMap(Arrays::stream)
.collect(Collectors.toSet());
有关更多信息,您可以参考以下链接:
https://onlyfullstack.blogspot.com/2018/12/map-vs-flatmap-in-java-8.html