在 Java 8 中,Stream.map()
和 Stream.flatMap()
方法有什么区别?
map :: Stream T -> (T -> R) -> Stream R
,flatMap :: Stream T -> (T -> Stream R) -> Stream R
。
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
。
map
的映射器 lambda 返回 R
,flatMap
的映射器 lambda 返回 R
(Stream<R>
) 的 Stream
。 flatMap
的映射器返回的流被有效连接。否则,map
和 flatMap
都返回 Stream<R>
;不同之处在于映射器 lambda 的返回值,R
与 Stream<R>
。
map
和 flatMap
都可以应用于 Stream<T>
并且它们都返回 Stream<R>
。不同之处在于 map
运算为每个输入值生成一个输出值,而 flatMap
运算为每个输入值生成任意数量(零个或多个)值。
这反映在每个操作的参数中。
map
操作采用 Function
,它为输入流中的每个值调用并产生一个结果值,该值被发送到输出流。
flatMap
操作采用一个在概念上想要消耗一个值并产生任意数量的值的函数。但是,在 Java 中,方法返回任意数量的值是很麻烦的,因为方法只能返回零或一个值。可以想象一种 API,其中 flatMap
的映射器函数接受一个值并返回一个数组或一个 List
值,然后将其发送到输出。鉴于这是流库,表示任意数量的返回值的一种特别恰当的方法是映射器函数本身返回一个流!映射器返回的流中的值从流中排出并传递给输出流。每次调用映射器函数返回的值的“团块”在输出流中根本没有区别,因此输出被称为“扁平化”。
如果要发送零值,典型用途是 flatMap
的映射器函数返回 Stream.empty()
,如果要返回多个值,则返回 Stream.of(a, b, c)
之类的东西。但是当然可以返回任何流。
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 个例子来获得 更多 实用的观点:
第一个使用 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 流。 它不是整数流!
如果必须使用转换函数(通过映射),那么首先必须将流展平为其他东西(a整数流)。
如果移除 flatMap
,则返回以下错误:运算符 + 未定义参数类型 List, int。 strong>
不可能对 List
个整数应用 + 1!
Stream<Integer>
流而不是 Integer
流。
请完整阅读帖子以获得清晰的想法,
地图与平面地图:
要从列表中返回每个单词的长度,我们将执行如下操作。
下面给出的简短版本
当我们收集两个列表时,如下所示
没有平面地图 => [1,2],[1,1] => [[1,2],[1,1]] 这里有两个列表放在一个列表中,所以输出将是包含列表的列表
使用平面地图 => [1,2],[1,1] => [1,2,1,1] 这里有两个列表被展平,只有值放在列表中,所以输出将是仅包含元素的列表
基本上它将所有对象合并为一个
## 详细版本如下:-
例如:- 考虑一个列表 [“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());
}
}
这里的问题是,传递给 map 方法的 Lambda 为每个单词返回一个 String 数组,所以 map 方法返回的流实际上是 Stream 类型,但是我们需要的是 Stream 来表示一个字符流,下图说明了问题。
图一:
https://i.stack.imgur.com/0GRsT.png
你可能会想,我们可以使用 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>),相反,我们必须首先将每个单词转换为一个单独的字母数组,然后将每个数组变成一个单独的流
通过使用 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());
https://i.stack.imgur.com/yf3vz.png
flatMap 方法允许您用另一个流替换流的每个值,然后将所有生成的流连接到一个流中。
单行答案:flatMap
有助于将 Collection<Collection<T>>
扁平化为 Collection<T>
。同样,它也会将 Optional<Optional<T>>
扁平化为 Optional<T>
。
https://i.stack.imgur.com/7e1tY.jpg
如您所见,仅使用 map()
:
中间类型是 Stream>
返回类型是 List>
并使用 flatMap()
:
中间类型是 Stream
返回类型是 List
这是下面使用的代码的测试结果:
-------- 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 用于 A -> B 映射
Stream.of("dog", "cat") // stream of 2 Strings
.map(s -> s.length()) // stream of 2 Integers: [3, 3]
它将任何项目 A
转换为任何项目 B
。 Javadoc
.flatMap 用于 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]
它 --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 , 猫]。
您传递给 stream.map
的函数必须返回一个对象。这意味着输入流中的每个对象都会在输出流中产生一个对象。
您传递给 stream.flatMap
的函数为每个对象返回一个流。这意味着该函数可以为每个输入对象返回任意数量的对象(包括一个对象)。然后将生成的流连接到一个输出流。
Department
流。每个部门都有 0 到 n 个Employee
。您需要的是所有员工的流。所以你会怎么做?您编写了一个 flatMap 方法,该方法接受一个部门并返回其员工流。
flatMap
的主要原因?我怀疑这可能是偶然的,并没有说明关键用例或 flatMap
存在的原因。 (下面继续……)
flatMap
背后的主要动机是适应使用 map
时可能出现的错误。您如何处理原始集合中的一个或多个项目无法映射到输出项目的情况?通过为每个输入对象引入一个中间集(例如 Optional
或 Stream
),flatMap
允许您排除“无效”输入对象(或本着 stackoverflow.com/a/52248643/107158 的精神所谓的“坏苹果” ) 从最后一组。
对于 Map 我们有一个元素列表和一个 (function,action) f 所以:
[a,b,c] f(x) => [f(a),f(b),f(c)]
对于平面地图,我们有一个元素列表,我们有一个 (function,action) f,我们希望结果是扁平的:
[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]
我有一种感觉,这里的大多数答案都使简单的问题过于复杂。如果您已经了解 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)
List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(i -> i) .collect(Collectors.toList());
。它应该是 Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(List::stream) .collect(Collectors.toList());
地图()和平面地图()
地图()
只需要一个 Function
Stream
.of(1,2,3,4,5)
.map(myInt -> "preFix_"+myInt)
.forEach(System.out::println);
它只需要类型 Integer
的元素 1 到 5,使用每个元素从类型 String
构建一个新元素,其值为 "prefix_"+integer_value
并将其打印出来。
平面图()
知道 flatMap() 采用函数 F<T, R>
是很有用的,其中
是一种可以从中构建 Stream 的类型。它可以是列表 (T.stream())、数组 (Arrays.stream(someArray)) 等。Stream 可以包含/或形成的任何内容。在下面的示例中,每个开发人员都有多种语言,因此 dev. Languages 是一个列表,将使用 lambda 参数。
是将使用 T 构建的结果流。知道我们有许多 T 实例,我们自然会有许多来自 R 的流。所有这些来自类型 R 的流现在将组合成来自类型 R 的单个“平面”流.
例子
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() 和使用 flatMap():
当流中 T 类型的每个元素都应该映射/转换为 R 类型的单个元素时,请使用 map()。结果是类型(1 个开始元素 -> 1 个结束元素)和新元素流的映射返回类型 R。
当流中 T 类型的每个元素都应该映射/转换为 R 类型元素的集合时,请使用 flatMap()。结果是类型的映射(1 个开始元素 -> n 个结束元素)。然后将这些集合合并(或展平)到 R 类型的新元素流。这对于表示嵌套循环很有用。
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()
.flatMap(foo -> foo.getMyBars().stream())
.forEach(bar -> System.out.println(bar.getMyName()));
Oracle 关于 Optional 的文章强调了 map 和 flatmap 之间的这种区别:
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
不幸的是,这段代码无法编译。为什么?变量 computer 是 Optional
String version = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
第一个 flatMap 确保返回 Optional
http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
我不太确定我应该回答这个问题,但每次我面对不理解这一点的人时,我都会使用相同的例子。
想象你有一个苹果。例如,map
正在将该苹果转换为 apple-juice
或 一对一 映射。
取同一个苹果,只从中取出种子,这就是 flatMap
所做的,或者 一对多,一个苹果作为输入,许多种子作为输出。
flatMap
案例,您是否首先将每个苹果的种子分别装在单独的袋子中,每个苹果一个袋子,然后再将所有袋子倒入一个袋子中?
flatmap
并不是真的懒惰,但自从 java-10 之后它是懒惰的
flatMap + lazy
,我敢打赌会有一些答案。
Map:- 此方法将一个函数作为参数并返回一个新流,该流包含通过将传递的函数应用于流的所有元素而生成的结果。
让我们想象一下,我有一个整数值列表( 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 ] -> [ {"history","math","geography"}, {"economics","biology"}, {"science","math"} ] -> 选择独特的科目 - > [经济学、生物、地理、科学、历史、数学]
http://codedestine.com/java-8-stream-flatmap-method/
如果您将 map()
视为一个迭代(一级 for
循环),则 flatmap()
是一个两级迭代(如嵌套的 for
循环)。 (输入每个迭代元素 foo
,然后执行 foo.getBarList()
并再次迭代该 barList
)
map()
:取一个流,对每个元素做一些事情,收集每个过程的单个结果,输出另一个流。 “做某事”的定义是隐含的。如果任何元素的处理导致 null
,则 null
用于组成最终流。因此,结果流中的元素数量将等于输入流的数量。
flatmap()
:获取 elements/streams 的流和一个函数(显式定义),将函数应用于每个流的每个元素,并将所有中间结果流收集为更大的流(“扁平化”)。如果任何元素的处理导致null
,则将空流提供给“展平”的最后一步。如果输入是多个流,则结果流中的元素数是所有输入中所有参与元素的总和。
通过阅读所有消息,理解的简单方法是:
如果您有一个平面元素列表,请使用 map:[0, 1, 2, 3, 4, 5]
如果您有一个元素列表列表,请使用 flatMap:[[1, 3, 5], [2, 4, 6]]。这意味着,您的列表需要展平,然后才能将地图操作应用于每个元素
简单的回答。
map
操作可以产生 Stream
的 Stream
.EX Stream<Stream<Integer>>
flatMap
操作只会产生 Stream
的东西。前 Stream<Integer>
这对于初学者来说是非常混乱的。基本区别是 map
为列表中的每个条目发出一个项目,而 flatMap
基本上是 map
+ flatten
操作。更清楚地说,当您需要多个值时使用 flatMap,例如,当您期望循环返回数组时,flatMap 在这种情况下将非常有用。
我已经写了一篇关于此的博客,您可以查看here。
流操作 flatMap
和 map
接受一个函数作为输入。
flatMap
期望函数为流的每个元素返回一个新流,并返回一个流,该流组合了函数为每个元素返回的流的所有元素。换句话说,使用 flatMap
,对于来自源的每个元素,函数将创建多个元素。 http://www.zoftino.com/java-stream-examples#flatmap-operation
map
期望函数返回一个转换后的值并返回一个包含转换后元素的新流。换句话说,使用 map
,对于来自源的每个元素,函数将创建一个转换后的元素。 http://www.zoftino.com/java-stream-examples#map-operation
如果您熟悉的话,也可以用 C# 进行很好的类比。基本上 C# Select
类似于 java map
和 C# SelectMany
java flatMap
。同样适用于 Kotlin 的集合。
不定期副业成功案例分享
flatMap
操作与 flat 完全相反。再一次,把它留给计算机科学家来改变一个术语。就像一个函数是“透明的”,这意味着你看不到它所做的任何事情,只能看到结果,而通俗地说你希望一个过程是透明的意味着你希望它的每一部分都被看到。