[英]What's the difference between map() and flatMap() methods in Java 8?
map
和flatMap
都可以應用於Stream<T>
並且它們都返回Stream<R>
。 不同之處在於map
操作為每個輸入值生成一個輸出值,而flatMap
操作為每個輸入值生成一個任意數量(零個或多個)的值。
這反映在每個操作的參數中。
map
操作接受一個Function
,它為輸入流中的每個值調用並產生一個結果值,該值被發送到輸出流。
flatMap
操作采用一個函數,該函數在概念上希望消耗一個值並生成任意數量的值。 但是,在 Java 中,方法返回任意數量的值很麻煩,因為方法只能返回零或一個值。 可以想象一個 API,其中flatMap
的 mapper 函數接受一個值並返回一個數組或一個值List
,然后將它們發送到輸出。 鑒於這是流庫,表示任意數量返回值的一種特別合適的方式是讓映射器函數本身返回流! 映射器返回的流中的值從流中排出並傳遞到輸出流。 每次調用映射器函數返回的值的“塊”在輸出流中根本沒有區別,因此輸出被稱為“扁平化”。
如果要發送零值,則flatMap
的映射器函數的典型用途是返回Stream.empty()
Stream.of(a, b, c)
如果要返回多個值,則返回Stream.of(a, b, c)
之類的東西。 但是當然可以返回任何流。
Stream.flatMap
,顧名思義,是map
和flat
操作的結合。 這意味着您首先將函數應用於元素,然后將其展平。 Stream.map
僅將函數應用於流而不Stream.map
。
要了解扁平化流包含什么,請考慮像[ [1,2,3],[4,5,6],[7,8,9] ]
這樣的結構,它具有“兩個級別”。 扁平化這意味着將其轉換為“一級”結構: [ 1,2,3,4,5,6,7,8,9 ]
。
我想舉兩個例子以獲得更實際的觀點:
使用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);
}
在第二個示例中,傳遞了一個列表流。 它不是整數流!
如果必須使用轉換函數(通過映射),那么首先必須將 Stream 展平為其他內容(整數 Stream)。
如果刪除flatMap
則返回以下錯誤:運算符 + 未定義參數類型 List, int。
不可能在整數List
上應用 + 1!
請仔細閱讀帖子以獲得清晰的想法,
地圖與平面地圖:
要從列表中返回每個單詞的長度,我們將執行如下操作。
當我們收集兩個列表時,如下所示
沒有平面地圖=> [1,2],[1,1] => [[1,2],[1,1]]這里兩個列表放在一個列表中,所以輸出將是包含列表的列表
使用flat map => [1,2],[1,1] => [1,2,1,1]這里兩個列表被扁平化,只有值放在列表中,所以輸出將是只包含元素的列表
基本上它將所有對象合並為一個
##詳細版本如下:-
例如:-
考慮一個列表[“STACK”, ”OOOVVVER”]並且我們試圖返回一個類似[“STACKOVER”]的列表(僅返回該列表中的唯一字母)最初,我們將執行如下操作以返回一個列表[“STACKOVER” ”]來自[“堆棧”,“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來表示字符流,下圖說明了問題。
圖一:
你可能會認為,我們可以使用 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 中的地圖進行比較。圖 B
flatMap 方法允許您用另一個流替換流的每個值,然后將所有生成的流連接到一個流中。
一行答案: 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);
}
}
您傳遞給stream.map
的函數必須返回一個對象。 這意味着輸入流中的每個對象都會在輸出流中產生一個對象。
您傳遞給stream.flatMap
的函數為每個對象返回一個流。 這意味着該函數可以為每個輸入對象返回任意數量的對象(包括無)。 然后將生成的流連接到一個輸出流。
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'
的代碼等。同樣,為了說明的目的,它表示為[狗貓]。
對於 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()
方法旨在通過避免包裝來克服這一點。
例子:
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)
Oracle 關於 Optional 的文章強調了 map 和 flatmap 之間的這種區別:
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
不幸的是,這段代碼不能編譯。 為什么? 變量 computer 的類型為
Optional<Computer>
,因此調用 map 方法是完全正確的。 但是,getSoundcard() 返回一個 Optional 類型的對象。 這意味着地圖操作的結果是一個Optional<Optional<Soundcard>>
類型的對象。 因此,對 getUSB() 的調用無效,因為最外層的 Optional 包含另一個 Optional 作為其值,這當然不支持 getUSB() 方法。對於流, flatMap 方法接受一個函數作為參數,它返回另一個流。 此函數應用於流的每個元素,這將產生流的流。 但是, flatMap 具有將每個生成的流替換為該流的內容的效果。 換句話說,該函數生成的所有獨立流都合並或“扁平化”為一個流。 我們在這里想要的是類似的東西,但我們想將一個兩級的 Optional “扁平化”為一個。
Optional 還支持 flatMap 方法。 它的目的是將轉換函數應用於 Optional 的值(就像 map 操作一樣),然后將生成的兩級 Optional 扁平化為單個 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() 返回一個 String 而不是一個 Optional 對象。
http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
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,使用每個元素從類型為"prefix_"+integer_value
String
類型構建一個新元素並將其打印出來。
flatMap()
知道 flatMap() 需要一個函數F<T, R>
其中很有用
T 是一種可以從中構建 Stream的類型。 它可以是一個列表 (T.stream())、一個數組 (Arrays.stream(someArray)) 等等。任何可以與/或形成 Stream 的東西。 在下面的示例中,每個開發人員都有多種語言,因此 dev. Languages 是一個列表,將使用 lambda 參數。
R 是將使用 T 構建的結果流。知道我們有許多 T 的實例,我們自然會有許多來自 R 的流。所有這些來自 R 類型的流現在將組合成一個來自 R 類型的“平面”流.
例子
Bachiri Taoufiq 的例子見這里的答案,簡單易懂。 為了清楚起見,假設我們有一個開發團隊:
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()
您可以“展平”事物,因為它采用上述結構
並將其變成{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 start element -> n end elements)的映射。 然后將這些集合合並(或展平)到一個新的 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
正在將蘋果轉換為apple-juice
一對一映射。
拿同一個蘋果,只從中取出種子,這就是flatMap
所做的,或者一對多,一個蘋果作為輸入,許多種子作為輸出。
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 作為輸入參數,並返回一個參數流作為返回值。 當這個函數應用於這個流的每個元素時,它會產生一個新值流。 然后將每個元素生成的這些新流的所有元素復制到一個新流中,這將是此方法的返回值。
讓我們想象一下,我有一個學生對象列表,每個學生可以選擇多個科目。
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"} ] -> 選擇獨特的科目 - > [經濟學、生物學、地理、科學、歷史、數學]
如果你認為map()
是一個迭代(一級for
循環), flatmap()
是一個二級迭代(就像一個嵌套的for
循環)。 (輸入每個迭代元素foo
,然后執行foo.getBarList()
並再次在該barList
迭代)
map()
:取一個流,對每個元素做一些事情,收集每個進程的單個結果,輸出另一個流。 “做某事”的定義是隱含的。 如果任何元素的處理結果為null
,則使用null
組成最終流。 因此,結果流中的元素數量將等於輸入流的數量。
flatmap()
:取一個元素/流流和一個函數(顯式定義),將該函數應用於每個流的每個元素,並將所有中間結果流收集為一個更大的流(“扁平化”)。 如果任何元素的處理結果為null
,則將空流提供給“展平”的最后一步。 如果輸入是多個流,則結果流中的元素數是所有輸入中所有參與元素的總數。
簡單的回答。
map
操作可以產生一個Stream
of Stream
.EX Stream<Stream<Integer>>
flatMap
操作只會產生Stream
的東西。 EX Stream<Integer>
這對初學者來說非常混亂。 基本的區別是map
為列表中的每個條目發出一個項目,而flatMap
基本上是map
+ flatten
操作。 更明確地說,當您需要多個值時使用 flatMap,例如,當您期望循環返回數組時, flatMap 在這種情況下將非常有用。
我寫了一篇關於這個的博客,你可以在這里查看。
流操作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()
還利用了流的部分惰性求值。 它將讀取第一個流,並且僅在需要時才會轉到下一個流。 此處詳細解釋了該行為: 是否保證 flatMap 是惰性的?
通過閱讀所有消息,簡單的理解方法是:
flat
的元素列表,請使用map
:[0, 1, 2, 3, 4, 5]flatMap
:[[1, 3, 5], [2, 4, 6]]。 這意味着,您的列表需要先展平,然后才能將地圖操作應用於每個元素Java 8 map() 操作以 T 類型的 Stream 作為輸入並產生 R 類型的結果 Stream。它將給定的映射器函數應用於輸入 Stream 的每個元素,並將結果存儲在輸出 Stream 中。
map() 操作為輸入 Stream 的每個值生成一個值,因此它也稱為一對一映射。
讓我們舉一個例子,我們想要返回列表中每個單詞的字符數。 為此,我們需要將該函數應用於列表的每個元素。 這個函數的工作是接受單詞並返回工作的長度。
map() 示例
public class MapExample {
public static void main(String[] args) {
List<String> stringList = Arrays.asList("Java Programming", "Java", "Spring Boot", "Java 8", "Hibernate", "Oracle");
List<Integer> collect = stringList.stream()
.map(String::length) // :: means method references
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
}
輸出::
16
4
11
6
9
6
map() 只做映射,但 flatMap() 執行映射和展平。 扁平化意味着將數據從 Stream<Stream> 轉換為 Stream。 這是 map() 和 flatMap() 之間的主要區別。
flatMap() 操作將 Stream T 作為輸入並生成 R 類型的結果流。它的映射器函數為輸入流的每個值生成多個值,並將這些多個值展平為結果流。
我們以 flatMap() 為例
public class FlatMapExample {
public static void main(String[] args)
{
List<List<Integer> > number = new ArrayList<>();
number.add(Arrays.asList(8, 4));
number.add(Arrays.asList(43, 23));
number.add(Arrays.asList(50, 26));
number.add(Arrays.asList(73, 83));
System.out.println("List of list or (Arrays of Array) - " + number);
List<Integer> flatList
= number.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
System.out.println("List generate by flatMap -"
+ flatList);
}
}
輸出::
List of list or (Arrays of Array) - [[8, 4], [43, 23], [50, 26], [73, 83]]
List generate by flatMap -[8, 4, 43, 23, 50, 26, 73, 83]
Java 8中的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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.