Java 8中的map和flatMap方法有什么区别?
在Java 8中, Stream.map
和Stream.flatMap
方法有什么区别?
map
和flatMap
都可以应用到Stream<T>
,它们都返回一个Stream<R>
。 区别在于map
操作为每个输入值生成一个输出值,而flatMap
操作为每个输入值生成一个任意数字(零个或多个)值。
这反映在每个操作的参数中。
map
操作接受一个Function
,该Function
为输入流中的每个值调用,并生成一个结果值,并将其发送到输出流。
flatMap
操作采用了一个函数,它在概念上要消耗一个值并产生任意数量的值。 但是,在Java中,返回任意数量的值的方法很麻烦,因为方法只能返回零个或一个值。 可以想象一个API,其中flatMap
的mapper函数获取一个值并返回一个数组或值List
,然后将其发送到输出。 鉴于这是流库,表示任意数量的返回值的一种特别合适的方式是映射器函数本身返回一个流! 映射器返回的流的值将从流中排出并传递到输出流。 每次调用映射器函数返回的值的“块”在输出流中根本不会被区分,因此输出被称为“平坦化”。
典型的用法是flatMap
的mapper函数返回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 ]
。
我想举两个例子来得到更实际的观点:
使用地图的第一个例子:
@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); }
没有什么特别的第一个例子,一个函数被用来返回大写的字符串。
使用平面图的第二个例子:
@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); }
在第二个例子中,一个列表流被传递。 这不是一个整数流!
如果必须使用变换函数(通过映射),那么首先将流展开为其他内容(整数流)。
如果flatMap被移除,则返回以下错误: operator +未定义为参数类型List,int。
无法在整数列表上应用+1!
请完整地阅读帖子,以获得清晰的想法, 地图vs flatMap:要从列表中返回每个单词的长度,我们会做下面的事情。
例如:-
考虑一个列表[“STACK”,“OOOVVVER”] ,我们试图返回一个像[“STACKOVER”] (从列表中返回唯一的字母)的列表。最初我们会做下面的事情来返回一个列表[“STACKOVER” ] [“堆叠”,“覆盖”]
这里的问题是,传递给map方法的Lambda为每个单词返回一个String数组,所以map方法返回的流实际上是Stream类型的,但是我们需要的是Stream来表示一个字符流,下图展示了问题。
图A:
你可能会认为,我们可以使用flatmap来解决这个问题,
好的,让我们看看如何通过使用map和Arrays.stream来解决这个问题首先,你需要一个字符流而不是数组流。 有一个名为Arrays.stream()的方法,它将获取一个数组并生成一个流,例如:
上面的代码仍然不起作用,因为我们现在最终得到一个流列表(更准确地说,Stream>),相反,我们必须首先将每个单词转换为一个单独的字母数组,然后将每个数组转换为一个单独的流
通过使用flatMap我们应该能够解决这个问题如下:
flatMap将执行映射每个数组不是流,但与该流的内容。使用映射(Arrays :: stream)时将生成的所有单个流合并到一个单一的流。 图B说明了使用flatMap方法的效果。 将它与图A中的图进行比较
flatMap方法允许用另一个流替换一个流的每个值,然后将所有生成的流加入到一个流中。
传递给stream.map
的函数必须返回一个对象。 这意味着输入流中的每个对象都会导致输出流中只有一个对象。
传递给stream.flatMap
的函数为每个对象返回一个流。 这意味着该函数可以为每个输入对象返回任意数量的对象(包括无)。 所得到的流然后连接到一个输出流。
甲骨文在“可选”中的文章强调了map和flatmap之间的区别:
String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");
不幸的是,这段代码不能编译。 为什么? 变量计算机的类型是
Optional<Computer>
,因此调用映射方法是完全正确的。 但是,getSoundcard()返回一个类型为Optional的对象。 这意味着映射操作的结果是一个Optional<Optional<Soundcard>>
类型的对象。 因此,对getUSB()的调用是无效的,因为最外层的Optional包含另一个Optional,当然这个方法不支持getUSB()方法。使用流,flatMap方法将函数作为参数,返回另一个流。 此函数应用于流的每个元素,这将导致流的流。 但是,flatMap具有将每个生成的流替换为该流的内容的效果。 换句话说,函数生成的所有单独的流都会合并或“拼合”为一个单独的流。 我们在这里想要的是类似的东西,但是我们想要将一个两层的可选项“拼合”成一个 。
可选还支持flatMap方法。 其目的是将转换函数应用于Optional的值(就像map操作一样),然后将生成的两级可选项平铺为一个可选项 。
所以,为了使我们的代码正确,我们需要使用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
映射: – 此方法将一个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]
如你所见,output是一个新的流,其值是输入流的每个元素返回的流的所有元素的集合。
[S1,S2,S3] – > [{“history”,“math”,“geography”},{“economics”,“biology”},{“science”,“math”}] > [经济,生物,地理,科学,历史,数学]
我有一种感觉,在这里所有的答案过度地处理这个问题。 如果您已经了解了map
工作原理,那么应该相当简单地掌握。
flatmap()
方法是一种特殊的map()
,它可以使嵌套的结构变平坦或避免结束它们。
例子:
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) .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我们有一个元素列表和一个(function,action)f如此:
[a,b,c] f(x) => [f(a),f(b),f(c)]
而对于平面地图,我们有一个元素列表列表,我们有一个(函数,行动)f,我们希望结果是平坦的:
[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]
如果您熟悉的话,也可以使用C#。 基本上C# Select
类似于java的map
和C#的SelectMany
java flatMap
。 同样适用于Kotlin的收藏。
这对于初学者来说非常混乱。 基本的区别是map
为列表中的每个条目发出一个条目,而flatMap
基本上是一个map
+ flatten
操作。 更清楚的是,当你需要多个值的时候,使用flatMap,比如当你期望一个循环返回数组的时候,在这种情况下,flatMap将会非常有用。
我已经写了一个关于这个的博客,你可以在这里看看。