Stream.map()
和Stream.flatMap()
方法有什么区别?map
和flatMap
都可以应用于Stream<T>
,它们都返回一个Stream<R>
。区别在于map
操作为每个输入值产生一个输出值,而flatMap
操作为每个输入值产生一个任意数量(零个或更多)的值。
这体现在每个操作的参数上。
map
操作接受一个Function
,该函数对输入流中的每个值进行调用,并产生一个结果值,该结果值被发送到输出流。
flatMap
操作接受一个希望消耗一个值并产生任意数量的值概念上的函数。然而,在Java中,方法要返回任意数量的值很麻烦,因为方法只能返回零个或一个值。可以想象一下,如果mapper函数为flatMap
接受一个值并返回一个值数组或List
,这些值将被发送到输出。考虑到这是streams库,表示任意数量的返回值的一种特别适合的方式是让mapper函数本身返回一个stream!来自mapper返回的stream的值从stream中排出并传递到输出stream中。每次调用mapper函数返回的值“成群地”不会在输出stream中有任何区别,因此输出被称为已“平坦化”。
通常使用flatMap
的mapper函数,如果要发送零个值,则返回Stream.empty()
,如果要返回多个值,则类似于Stream.of(a, b, c)
。但当然可以返回任何stream。
Stream.flatMap
,顾名思义,是map
和flat
操作的组合。这意味着您首先对元素应用函数,然后将其平铺。而Stream.map
仅将函数应用于流,而不会将流平铺。
要理解什么是“平铺”流,考虑一个结构如[ [1,2,3],[4,5,6],[7,8,9] ]
,它有“两层”结构。将其平铺意味着将其转换为“一层”结构:[ 1,2,3,4,5,6,7,8,9 ]
。
flatMap
这种方法。每个答案都适合回答社区可能寻求的不同类型问题。 - aff我想给出两个例子来提供一个更实际的观点:
第一个例子使用了 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
的Stream
。它不是Integer
的Stream
!
map
),则首先必须将Stream
展平为其他内容(Integer
的Stream
)。
flatMap
,则会返回以下错误:运算符+对于参数类型(List、int)未定义。
List
的Integer
应用+1操作!Stream<Integer>
的流而不是一个 Integer
的流。 - payne请完整阅读文章以获得清晰的理解
map和flatMap:
如果我们想要返回一个列表中每个单词的长度,我们可以像下面这样做..
当我们收集两个列表时,如下所示
没有使用 flatMap => [1,2],[1,1] => [[1,2],[1,1]] 这里将两个列表放入一个列表中,因此输出将是包含列表的列表
使用了 flatMap => [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来表示字符流,下面的图示说明了这个问题。
图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>),相反,我们必须先将每个单词转换为单独字母的数组,然后将每个数组转换为单独的流。
通过使用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中map的效果进行比较。
图BflatMap方法允许您将流的每个值替换为另一个流,然后将所有生成的流合并到单个流中。
如您所见,仅使用 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);
}
}
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() 方法返回 int 值。因此,实际的集合将是:[100, 111, 103, 99, 97, 116]
,其中 100
是字符 'd' 的代码,111
是字符 'o' 的代码等。同样,为了说明目的,它被表示为 [d,o,g,c,a,t]。
您传递给stream.map
的函数必须返回一个对象。这意味着输入流中的每个对象都会在输出流中产生一个对象。
您传递给stream.flatMap
的函数为每个对象返回一个流。这意味着该函数可以为每个输入对象返回任意数量的对象(包括没有)。然后将生成的流连接到一个输出流中。
flatMap
的主要原因?我怀疑这可能只是偶然的,没有阐明 flatMap
存在的关键用例或原因。(以下继续...) - Derek Maharstream.filter
方法来实现这个功能。 - undefined对于一个 Map,我们有一个元素列表和一个(函数,操作)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)]
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)
Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
.flatMap(List::stream)
.collect(Collectors.toList());
- arthurmap() 和 flatMap()
map()
它只需一个函数 <T, R>,其中 T 是元素,R 是使用 T 构建的返回元素。最后,我们将得到一个类型为 R 的流。一个简单的示例可以是:
Stream
.of(1,2,3,4,5)
.map(myInt -> "preFix_"+myInt)
.forEach(System.out::println);
这段代码简单地使用Type Integer
的1到5个元素,用每个元素构建一个新元素,类型为String
,值为"prefix_"+integer_value
,并将其打印出来。
flatMap()
了解flatMap()
非常有用,它接受一个函数F<T, R>
,其中:
T是一种类型,可以从中构建/使用Stream。它可以是List(T.stream()),数组(Arrays.stream(someArray))等任何可以用Stream构建/形成的东西。在下面的示例中,每个开发人员都有很多语言,因此dev.Languages是一个List,并将使用lambda参数。
R是将使用T构建的结果流。知道我们有许多T的实例,我们自然会从R中拥有许多流。所有这些来自Type R的流现在将合并为一个单一的“扁平”流,来自Type 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}
应用 Stream.map() 在 dev_team 上以获取每个开发人员的语言:
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()));
map :: Stream T -> (T -> R) -> Stream R
,flatMap :: Stream T -> (T -> Stream R) -> Stream R
。 - Chris Martin<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
。 - Stuart Marksmap
方法的映射函数返回类型为R
,而flatMap
方法的映射函数返回类型为Stream<R>
。flatMap
方法返回的流是将所有映射后的Stream<R>
连接起来的结果。两种方法都返回类型为Stream<R>
的流,区别在于映射函数的返回值类型,一个是R
,一个是Stream<R>
。 - derekmmap
。如果每个元素将被转换为多个值,并且需要展平结果流,则使用flatMap
。 - DimaSan