ความแตกต่างระหว่าง map () และ flatMap () วิธีการใน Java 8 คืออะไร?


709

ใน Java 8 ความแตกต่างระหว่างStream.map()และStream.flatMap()วิธีการคืออะไร


56
ประเภทลายเซ็นบอกเล่าเรื่องราวทั้งหมด map :: Stream T -> (T -> R) -> Stream R, flatMap :: Stream T -> (T -> Stream R) -> Stream R.
Chris Martin

99
fwiw, ลายเซ็นประเภทเหล่านั้นไม่ได้ดูเหมือนจาวา (ฉันรู้ว่าฉันรู้ - แต่บอกว่ามันบอกว่า "เรื่องราวทั้งหมด" wrt map / flatMap ถือว่ามีความรู้มากมายเกี่ยวกับสิ่งใหม่ & การปรับปรุง "Java ++")
michael

16
@michael ลายเซ็นประเภทนั้นดูเหมือน Haskell ไม่ใช่ Java แต่มันก็ไม่ชัดเจนว่าลายเซ็น Java จริงใด ๆ <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)สามารถอ่านเพิ่มเติมได้ที่:
Stuart Marks

8
ฮาใช่ฉันหมายถึง "Java จริง" เช่นเดียวกับ C ++ Java สมัยใหม่แทบจะไม่เป็นที่รู้จักสำหรับใครก็ตามที่เริ่มใช้มันใน 90s (เหมือนที่ฉันทำทั้งสองภาษา) เพียงแค่ตอบกลับความคิดเห็นวิธีการดังกล่าวแทบจะไม่บอกเลยว่า "เรื่องราวทั้งหมด" อย่างน้อยก็ไม่ใช่อีกต่อไปไม่ใช่โดยไม่มีการอธิบายเพิ่มเติม (หรือในกรณีของผู้แสดงความคิดเห็นแปล)
ไมเคิล

2
ซึ่งก็คือการพูดว่าmapแลมบ์รา mapper กลับRมาเป็นflatMapแลมบ์รา mapper กลับมาStreamของR( Stream<R>) กระแสข้อมูลที่ถูกส่งคืนโดยผู้flatMapทำแผนที่จะถูกต่อกันอย่างมีประสิทธิภาพ มิฉะนั้นทั้งสองmapและflatMapกลับมาStream<R>; ความแตกต่างเป็นสิ่งที่ lambdas mapper กลับ, เทียบกับR Stream<R>
derekm

คำตอบ:


817

ทั้งสองmapและflatMapสามารถนำไปใช้และพวกเขาทั้งสองกลับStream<T> Stream<R>ความแตกต่างคือการmapดำเนินการสร้างค่าผลลัพธ์หนึ่งค่าสำหรับแต่ละค่าอินพุตในขณะที่การflatMapดำเนินการสร้างค่าตัวเลข (ศูนย์หรือมากกว่า) สำหรับแต่ละค่าอินพุต

สิ่งนี้สะท้อนให้เห็นในข้อโต้แย้งของแต่ละการปฏิบัติการ

การmapดำเนินการใช้เวลาFunctionซึ่งเรียกว่าสำหรับแต่ละค่าในสตรีมอินพุตและสร้างค่าผลลัพธ์หนึ่งค่าซึ่งส่งไปยังเอาต์พุตสตรีม

การflatMapดำเนินการใช้ฟังก์ชันที่แนวคิดต้องการใช้หนึ่งค่าและสร้างจำนวนตามอำเภอใจ อย่างไรก็ตามใน Java เป็นวิธีที่ยุ่งยากในการส่งคืนค่าตามอำเภอใจเนื่องจากเมธอดสามารถส่งคืนค่าศูนย์หรือค่าเดียวเท่านั้น หนึ่งอาจจินตนาการ API ที่ฟังก์ชั่น mapper flatMapใช้ค่าและส่งกลับอาร์เรย์หรือListของค่าซึ่งถูกส่งไปยังเอาต์พุตแล้ว ระบุว่านี่คือไลบรารีสตรีมวิธีที่เหมาะสมโดยเฉพาะอย่างยิ่งในการแสดงจำนวนค่าส่งคืนโดยพลการคือสำหรับฟังก์ชั่น mapper ตัวเองเพื่อกลับกระแส! ค่าจากสตรีมที่ส่งคืนโดย mapper จะถูกระบายจากสตรีมและส่งผ่านไปยังเอาต์พุตสตรีม "clumps" ของค่าที่ส่งคืนโดยการเรียกไปยังฟังก์ชัน mapper แต่ละครั้งจะไม่แตกต่างกันเลยในเอาต์พุตสตรีมดังนั้นเอาต์พุตจะถูกกล่าวว่าเป็น "แบน"

การใช้งานทั่วไปสำหรับฟังก์ชั่น mapper ที่flatMapจะกลับมาStream.empty()ถ้ามันต้องการที่จะส่งค่าเป็นศูนย์หรือสิ่งที่ต้องการStream.of(a, b, c)ถ้ามันจะกลับมาหลายค่า แต่แน่นอนสามารถส่งกระแสข้อมูลใด ๆ


26
ฟังดูฉันชอบการflatMapดำเนินการที่ตรงข้ามกับที่แน่นอน อีกครั้งปล่อยให้นักวิทยาศาสตร์คอมพิวเตอร์เปลี่ยนคำของมัน เช่นเดียวกับฟังก์ชั่นที่ "โปร่งใส" หมายถึงคุณไม่สามารถมองเห็นสิ่งที่มันทำเพียงผลลัพธ์ในขณะที่พูดว่าคุณต้องการให้กระบวนการโปร่งใสหมายถึงคุณต้องการให้ทุกส่วนของมันเห็น
coladict

45
@coladict ลองดูจากมุมมองที่แตกต่าง: มันไม่ใช่กรณีแบบโปร่งใสที่คุณสามารถดูผลงานภายในได้ แต่ฟังก์ชั่นทั้งหมดมีความโปร่งใสคือมองไม่เห็นคุณ - ในขณะที่ยังทำงานอยู่และให้คุณเห็นสิ่งที่คุณต้องการ ' กำลังทำงานกับ ในกรณีนี้ "แบน" หมายถึงตรงข้ามกับ "ซ้อน" แฟลตแมปจะลบหนึ่งระดับการซ้อนโดยการแบน
Zefiro

7
@coladict สิ่งที่ "โปร่งใส" กินหัวฉันมาหลายปีแล้ว ดีใจที่ได้รู้จักคนอื่นอย่างน้อยหนึ่งคนรู้สึกแบบเดียวกัน
Ashok Bijoy Debnath

9
การแบนมาจากการเปลี่ยนโครงสร้าง 2 ระดับเป็นโครงสร้างระดับเดียวดูคำตอบของ Dici สำหรับตัวอย่างstackoverflow.com/a/26684582/6012102
andrzej.szmukala

26
นี่คือคำอธิบายที่ดีที่สุดของflatMap นี่คือสิ่งที่ทำให้คลิกทั้งหมด: ค่าจากสตรีมที่ส่งคืนโดยผู้ทำแผนที่จะถูกระบายออกจากสตรีมและส่งผ่านไปยังสตรีมเอาต์พุต "การกระจุก" ของค่าส่งกลับโดยการโทรแต่ละฟังก์ชั่น mapper จะไม่ประสบความสำเร็จที่ทุกคนในกระแสออกจึงเอาท์พุทบอกว่าจะได้รับการ "แบน" ขอบคุณ!
neevek

465

Stream.flatMapมันสามารถเดาได้ด้วยชื่อของมันคือการรวมกันของ a mapและการflatดำเนินการ นั่นหมายความว่าคุณใช้ฟังก์ชันกับองค์ประกอบของคุณก่อนแล้วจึงปรับให้แบน Stream.mapใช้ฟังก์ชั่นกับสตรีมโดยไม่ทำให้กระแสแบน

เพื่อทำความเข้าใจว่ากระแสข้อมูลที่แบนราบประกอบด้วยอะไรพิจารณาโครงสร้าง[ [1,2,3],[4,5,6],[7,8,9] ]ที่มี "สองระดับ" แฟบนี้หมายถึงเปลี่ยนมันใน "ระดับหนึ่ง" [ 1,2,3,4,5,6,7,8,9 ]โครงสร้าง:


6
เรียบง่ายและแสนหวาน
ประเสริฐ

3
ฮ่าฮ่าเพื่อความเป็นธรรมฉันยังคงประหลาดใจเมื่อเห็นว่าปริมาณการใช้คำถามนี้ได้รับ ข้อสังเกตที่ตลกอีกข้อหนึ่งคือมันเป็นเวลาเกือบ 5 ปีแล้วที่ฉันเขียนคำตอบนี้และมีรูปแบบของการ upvoting ที่ค่อนข้างสอดคล้องกันซึ่งคำตอบที่ยอมรับนั้นได้รับ upvotes ประมาณสอง upvote สำหรับทุกคำตอบที่ฉันได้รับ มันสอดคล้องกันอย่างน่าประหลาดใจ
Dici

1
นี่ไม่ใช่คำตอบที่ยอมรับได้อย่างไรขอบคุณสำหรับการตรงไปยังจุดและวางตัวอย่างง่าย ๆ
1mike12

234

ฉันอยากจะให้ 2 ตัวอย่างเพื่อให้ได้มุมมองที่เป็นประโยชน์มากขึ้น :
ตัวอย่างแรกที่ใช้แผนที่:

@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);
}   

ไม่มีอะไรพิเศษในตัวอย่างแรก a 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);
}

ในตัวอย่างที่สองสตรีมของรายการถูกส่งผ่าน ไม่ใช่กระแสของจำนวนเต็ม!
หากจำเป็นต้องใช้ฟังก์ชั่นการแปลง (ผ่านแผนที่) อันดับแรกสตรีมจะต้องถูกแบนให้เป็นอย่างอื่น (สตรีมของจำนวนเต็ม)
หาก flatMap ถูกลบออกดังนั้นข้อผิดพลาดต่อไปนี้จะถูกส่งกลับ: ตัวดำเนินการ + ไม่ได้กำหนดไว้สำหรับรายการประเภทอาร์กิวเมนต์, int
เป็นไปไม่ได้ที่จะใช้ +1 กับรายชื่อจำนวนเต็ม!


@PrashanthDebbadwar ฉันคิดว่าคุณจะจบลงด้วยกระแสของมากกว่ากระแสStream<Integer> Integer
เพย์น

166

กรุณาผ่านโพสต์อย่างเต็มที่เพื่อให้ได้ความคิดที่ชัดเจน

map vs flatMap:

ในการคืนความยาวของแต่ละคำจากรายการเราจะทำสิ่งที่ต้องการด้านล่าง ..

รุ่นสั้นที่ระบุด้านล่าง

เมื่อเรารวบรวมสองรายการรับด้านล่าง

ไม่มีแผนที่แบบแบน => [1,2], [1,1] => [1,2], [1,1]] ที่นี่มีสองรายการอยู่ภายในรายการดังนั้นผลลัพธ์จะเป็นรายการที่มีรายการ

ด้วยแผนที่แบน => [1,2], [1,1] => [1,2,1,1]นี่มีสองรายการที่แบนและมีเฉพาะค่าที่อยู่ในรายการดังนั้นผลลัพธ์จะเป็นรายการที่มีองค์ประกอบเท่านั้น

โดยพื้นฐานแล้วมันจะรวมวัตถุทั้งหมดเข้าด้วยกัน

## เวอร์ชันรายละเอียดได้รับไว้ด้านล่าง: -

ตัวอย่างเช่น: -
พิจารณารายการ[“ สแต็ค”,” OOOVVVER”]และเราพยายามที่จะส่งคืนรายการเช่น[“ สแต็คแวร์”] (ส่งกลับเฉพาะตัวอักษรที่ไม่ซ้ำจากรายการนั้น) เริ่มแรกเราจะทำสิ่งต่าง ๆ ด้านล่าง รายการ [“ 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 จะส่งคืนอาเรย์สตริงสำหรับแต่ละคำดังนั้นสตรีมที่ส่งคืนโดยวิธีการแมปจะเป็นประเภทสตรีมจริง ๆ แต่สิ่งที่เราต้องการคือสตรีมเพื่อเป็นตัวแทนสตรีมของตัวอักษร ปัญหา.

รูปที่:

ป้อนคำอธิบายรูปภาพที่นี่

คุณอาจคิดว่าเราสามารถแก้ปัญหานี้ได้โดยใช้ flatmap,
OK, ให้เราดูวิธีแก้ปัญหานี้โดยใช้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());

ด้านบนยังคงไม่ทำงานเพราะตอนนี้เราจบด้วยรายการสตรีม (แม่นยำยิ่งขึ้นสตรีม>) แต่ก่อนอื่นเราต้องแปลงแต่ละคำเป็นอาร์เรย์ของตัวอักษรแต่ละตัวแล้วทำให้แต่ละอาร์เรย์เป็นสตรีมแยกต่างหาก

โดยการใช้ 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 จะทำการแมปแต่ละแถวไม่ได้อยู่กับกระแส แต่ด้วยเนื้อหาของสายนั้น สตรีมแต่ละรายการทั้งหมดที่จะสร้างขึ้นขณะใช้แผนที่ (อาร์เรย์ :: สตรีม) จะถูกรวมเข้าในสตรีมเดียว รูปที่ B แสดงให้เห็นถึงผลกระทบของการใช้วิธีการ flatMap เปรียบเทียบกับสิ่งที่แผนที่ทำในรูป A. รูป B ป้อนคำอธิบายรูปภาพที่นี่

เมธอด flatMap ให้คุณแทนที่แต่ละค่าของสตรีมด้วยสตรีมอื่นจากนั้นรวมสตรีมที่สร้างทั้งหมดไว้ในสตรีมเดียว


2
คำอธิบายไดอะแกรมที่ดี
Hitesh

109

คำตอบหนึ่งบรรทัด: 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);
  }
}

8
ตัวอย่างที่คมชัดมาก .. จะไม่ใช้เวลามากกว่าสองสามวินาทีในการทำความเข้าใจแนวคิดกับตัวอย่างของคุณ ...
TechDog

2
คำอธิบายที่ดี ขอขอบคุณสำหรับคำอธิบายที่ง่ายและดีที่สุด
Sachin Rane

42

ฟังก์ชั่นที่คุณส่งผ่านจะstream.mapต้องส่งคืนวัตถุหนึ่งชิ้น นั่นหมายความว่าแต่ละวัตถุในอินพุตสตรีมส่งผลให้วัตถุหนึ่งรายการในสตรีมผลลัพธ์

ฟังก์ชั่นที่คุณส่งผ่านเพื่อstream.flatMapส่งคืนสตรีมสำหรับแต่ละวัตถุ นั่นหมายความว่าฟังก์ชั่นสามารถคืนค่าจำนวนวัตถุใด ๆ สำหรับแต่ละวัตถุอินพุต (รวมถึงไม่มี) จากนั้นสตรีมที่ได้จะถูกรวมเข้ากับสตรีมเอาต์พุตหนึ่งรายการ


ทำไมคุณต้องการที่จะ "คืนวัตถุจำนวนใด ๆ สำหรับแต่ละวัตถุอินพุต (รวมถึงไม่มี)"?
Derek Mahar

4
@DerekMahar จะมีกรณีการใช้งานมากมายสำหรับสิ่งนี้ ตัวอย่างเช่นสมมติว่าคุณมีกระแสข้อมูลDepartmentในองค์กรของคุณ แต่ละแผนกมีระหว่าง 0 และ n Employees สิ่งที่คุณต้องการคือพนักงานทุกคน แล้วคุณจะทำอย่างไร? คุณเขียนวิธี flatMap ซึ่งใช้แผนกและส่งคืนกระแสข้อมูลของพนักงาน
ฟิลิปป์

ฟิลิปป์ตัวอย่างของคุณแสดงเหตุผลหลักที่ใช้flatMapหรือไม่ ฉันสงสัยว่าอาจเป็นเรื่องบังเอิญและไม่ได้แสดงกรณีการใช้งานที่สำคัญหรือเหตุผลที่flatMapมีอยู่ (ต่อไปด้านล่าง ... )
Derek Mahar

หลังจากที่ได้อ่านdzone.com/articles/understanding-flatmapผมคิดว่าอยู่เบื้องหลังแรงจูงใจหลักคือเพื่อรองรับข้อผิดพลาดที่จะนำเสนอเมื่อใช้flatMap mapคุณจะจัดการกรณีที่รายการหนึ่งรายการขึ้นไปในชุดต้นฉบับไม่สามารถถูกแมปกับรายการผลลัพธ์ได้อย่างไร ด้วยการแนะนำชุดสื่อกลาง (พูดOptionalหรือStream) สำหรับแต่ละวัตถุอินพุตflatMapทำให้คุณสามารถยกเว้นวัตถุอินพุต "ไม่ถูกต้อง" (หรือที่เรียกว่า "แอปเปิ้ลไม่ดี" ในจิตวิญญาณของstackoverflow.com/a/52248643/107158 ) จาก ชุดสุดท้าย
Derek Mahar

1
@DerekMahar ใช่ stuations ที่แต่ละอินพุทวัตถุอาจหรืออาจไม่กลับมาเอาท์พุทวัตถุเป็นอีกกรณีการใช้งานที่ดีสำหรับแผนที่แบน
ฟิลิปป์

29

สำหรับแผนที่เรามีรายการองค์ประกอบและ a (ฟังก์ชั่นการกระทำ) 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)]

25

ฉันมีความรู้สึกว่าคำตอบส่วนใหญ่ที่นี่แก้ปัญหาง่าย ๆ ได้ หากคุณเข้าใจวิธีการ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)

ขอโทษ แต่ตัวอย่างที่ 2 จากจุดที่ 1 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());
อาร์เธอร์

@ Arthur ฉันคิดว่าฉันใช้ Vavr's Stream และ List ที่นี่ - แต่ฉันยอมรับว่ามันอาจจะสับสนนิดหน่อย - ฉันจะเปลี่ยนมันเป็น Java มาตรฐาน
Grzegorz Piwowarek

@GrzegorzPiwowarek แล้ว คำอธิบายง่ายๆนี้ล่ะ?
ยูจีน

22

บทความของออราเคิลเกี่ยวกับออพชั่นเน้นความแตกต่างระหว่างแผนที่และ flatmap นี้:

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");

น่าเสียดายที่รหัสนี้ไม่ได้รวบรวม ทำไม? คอมพิวเตอร์ตัวแปรเป็นประเภทOptional<Computer>ดังนั้นจึงถูกต้องสมบูรณ์ในการเรียกใช้วิธีการแมป อย่างไรก็ตาม getSoundcard () ส่งคืนออบเจ็กต์ประเภทเป็นตัวเลือก นี่หมายความว่าผลลัพธ์ของการดำเนินการแผนที่เป็นวัตถุประเภทOptional<Optional<Soundcard>>ซึ่งหมายความว่าผลการดำเนินงานแผนที่เป็นวัตถุของพิมพ์ดังนั้นการเรียกไปยัง getUSB () จึงไม่ถูกต้องเนื่องจากตัวเลือกที่อยู่นอกสุดมีค่าเป็นตัวเลือกอื่นซึ่งแน่นอนว่าไม่รองรับวิธี getUSB ()

ด้วย streams เมธอด flatMap รับหน้าที่เป็นอาร์กิวเมนต์ซึ่งส่งคืนสตรีมอื่น ฟังก์ชันนี้ใช้กับแต่ละองค์ประกอบของสตรีมซึ่งจะส่งผลให้สตรีมของสตรีม อย่างไรก็ตาม flatMap มีผลในการแทนที่แต่ละสตรีมที่สร้างโดยเนื้อหาของสตรีมนั้น กล่าวอีกนัยหนึ่งสตรีมที่แยกต่างหากทั้งหมดที่สร้างโดยฟังก์ชันจะถูกรวมเข้าด้วยกันหรือ "แบน" เป็นสตรีมเดียว สิ่งที่เราต้องการที่นี่คือสิ่งที่คล้ายกัน แต่เราต้องการ "แผ่" ตัวเลือกสองระดับเป็นหนึ่งเดียวระดับสองตัวเลือกเป็นหนึ่งใน

ตัวเลือกยังรองรับวิธีการ flatMap วัตถุประสงค์คือเพื่อใช้ฟังก์ชั่นการแปลงกับค่าของตัวเลือก (เช่นเดียวกับการดำเนินการแผนที่) แล้วแผ่ผลสองระดับทางเลือกให้เป็นตัวเดียวแผ่ส่งผลให้ระดับสองตัวเลือกเป็นหนึ่งเดียว

ดังนั้นเพื่อให้รหัสของเราถูกต้องเราต้องเขียนใหม่ดังต่อไปนี้โดยใช้ 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


1
คำถามคือเกี่ยวกับ Stream.map และ Stream.flatMap และไม่เกี่ยวกับ Optional.map anfd เป็นตัวเลือก .
flatMap

4
แต่มันช่วยฉันได้มากในการทำความเข้าใจปัญหาของฉันด้วยตัวเลือกและแบนแมปขอบคุณมาก!
Lo Junc

2
@djames เป็นคำตอบที่ถูกต้องสมบูรณ์อ่านได้จากย่อหน้า "ด้วย streams เมธอด flatMap จะทำหน้าที่เป็นอาร์กิวเมนต์ ... " :)
skwisgaar

ฉันคิดว่านี่เป็นประโยชน์อย่างมากสำหรับคำตอบอื่น ๆ ของที่นี่
Mz A

เวอร์ชัน flatMap () จะส่งค่า nullpointerexception ถ้าการ์ดเสียงเป็นโมฆะ ดังนั้นประโยชน์ที่สัญญาไว้ของตัวเลือกอยู่ที่ไหน
Ekaterina

16

ฉันไม่แน่ใจว่าฉันควรจะตอบคำถามนี้ แต่ทุกครั้งที่ฉันเผชิญหน้ากับใครบางคนที่ไม่เข้าใจฉันจะใช้ตัวอย่างเดียวกัน

ลองนึกภาพคุณมีแอปเปิ้ล A mapกำลังแปลงแอปเปิ้ลนั้นapple-juiceเป็นตัวอย่างหรือการทำแผนที่แบบหนึ่งต่อหนึ่ง

เอาแอปเปิ้ลนั้นออกมาแล้วเอาเฉพาะเมล็ดออกจากเมล็ดนั่นคือสิ่งที่flatMapทำหรือแอปเปิ้ลหนึ่งต่อหลาย ๆตัวเป็นอินพุทและหลาย ๆ เมล็ดออกมา


4
นั่นเป็นตัวอย่างที่น่าสนใจ :)
cassiomolin

ในflatMapกรณีนี้คุณเก็บเมล็ดจากแอปเปิลแต่ละถุงก่อนแยกถุงหนึ่งถุงต่อแอปเปิ้ลก่อนที่คุณจะเทถุงทั้งหมดลงในถุงเดียว?
Derek Mahar

@DerekMahar มันเคยเป็นคนยากจนในกระเป๋าเดียวก่อน java-10 ความหมายflatmapไม่ได้ขี้เกียจจริงๆ แต่ตั้งแต่ java-10 มันขี้เกียจ
Eugene

@Eugene โปรดอธิบายแนวคิดขี้เกียจอีกเล็กน้อยซึ่งคุณพยายามที่จะอธิบายไม่ชัดเจนสำหรับฉันฉันเข้าใจในสิ่งที่ derkerMahar อธิบายในความคิดเห็นมันเป็นสิ่งที่เกิดขึ้นก่อน java10?
JAVA

@ JAVA เพียงค้นหาflatMap + lazyฉันเดิมพันจะมีคำตอบ
ยูจีน

16

แผนที่ () และ flatMap ()

  1. map()

เพียงแค่ใช้ฟังก์ชั่นแลมบ์ดา param โดยที่ T คือองค์ประกอบและ R เป็นองค์ประกอบส่งคืนที่สร้างขึ้นโดยใช้ T ตอนท้ายเราจะมีสตรีมที่มีออบเจ็กต์ประเภทอาร์ตัวอย่างง่าย ๆ สามารถ:

Stream
  .of(1,2,3,4,5)
  .map(myInt -> "preFix_"+myInt)
  .forEach(System.out::println);

มันใช้องค์ประกอบ 1 ถึง 5 ของชนิดIntegerใช้แต่ละองค์ประกอบเพื่อสร้างองค์ประกอบใหม่จากชนิดที่Stringมีค่า"prefix_"+integer_valueและพิมพ์ออกมา

  1. flatMap()

มันมีประโยชน์ที่จะรู้ว่า flatMap () รับฟังก์ชั่นF<T, R>ที่

  • T เป็นประเภทจากที่สตรีมสามารถสร้างขึ้นจาก / กับ มันสามารถเป็นรายการ (T.stream ()), อาเรย์ (Arrays.stream (someArray)) ฯลฯ อะไรก็ได้ที่สตรีมสามารถมี / หรือแบบฟอร์ม ในตัวอย่างด้านล่างแต่ละ dev มีหลายภาษาดังนั้น dev ภาษาเป็นรายการและจะใช้พารามิเตอร์แลมบ์ดา

  • R คือกระแสที่เกิดขึ้นซึ่งจะถูกสร้างโดยใช้ T เมื่อรู้ว่าเรามีอินสแตนซ์จำนวนมากของ T เราจะมีลำธารมากมายจาก R โดยธรรมชาติแล้วกระแสทั้งหมดจาก Type R จะรวมกันเป็นสตรีมเดียวจาก Type 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}

ใช้ Stream.map ()บน dev_team เพื่อรับภาษาของแต่ละ dev:

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 () :

  • ใช้map()เมื่อองค์ประกอบแต่ละประเภท T จากสตรีมของคุณควรจะถูกแมป / แปลงเป็นองค์ประกอบเดียวของประเภท R ผลที่ได้คือการแมปประเภท(องค์ประกอบเริ่มต้น 1 - องค์ประกอบเริ่ม 1> องค์ประกอบปลาย 1)และกระแสใหม่ขององค์ประกอบประเภท R ถูกส่งคืน

  • ใช้ flatMap()เมื่อองค์ประกอบแต่ละประเภท T จากสตรีมของคุณควรจะแมป / เปลี่ยนเป็นชุดขององค์ประกอบของประเภท R ผลที่ได้คือการทำแผนที่ประเภท(องค์ประกอบเริ่มต้น 1 - องค์ประกอบสิ้นสุด> 1 คอลเลกชันเหล่านี้เป็นแล้วรวม (หรือบี้ ) เพื่อเป็นกระแสใหม่ขององค์ประกอบของประเภทอาร์นี้จะเป็นประโยชน์เช่นการเป็นตัวแทนของลูปซ้อนกัน

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()
    .flatMap(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));

11

แผนที่: - วิธีนี้ใช้ฟังก์ชั่นหนึ่งฟังก์ชั่นเป็นอาร์กิวเมนต์และส่งคืนสตรีมใหม่ที่ประกอบด้วยผลลัพธ์ที่สร้างขึ้นโดยใช้ฟังก์ชั่นที่ส่งผ่านไปยังองค์ประกอบทั้งหมดของสตรีม

ลองจินตนาการว่าฉันมีรายการค่าจำนวนเต็ม (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] -> [{"ประวัติ", "คณิตศาสตร์", "ภูมิศาสตร์"}, {"เศรษฐศาสตร์", "ชีววิทยา"}, {"วิทยาศาสตร์", "คณิตศาสตร์"}] -> เลือกวิชาที่ไม่ซ้ำกัน - > [เศรษฐศาสตร์, ชีววิทยา, ภูมิศาสตร์, วิทยาศาสตร์, ประวัติศาสตร์, คณิตศาสตร์]

http://codedestine.com/java-8-stream-flatmap-method/


สามารถสร้างความแตกต่างถ้าคุณให้รหัสแทนเพียงแค่การเชื่อมโยงเอกสาร doc
Charles-Antoine Fournel

11

.mapสำหรับการแมปA -> B

Stream.of("dog", "cat")              // stream of 2 Strings
    .map(s -> s.length())            // stream of 2 Integers: [3, 3]

จะแปลงรายการใดรายการใดAjavadocB


.flatMapสำหรับA -> สตรีม <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) แทนที่จะเป็นสตรีมของวัตถุ (สตรีม) แต่ก็ยังคงแสดงแนวคิดของ .flatMapก็ยังคงแสดงให้เห็นถึงความคิดของ

หมายเหตุ 2: แม้จะมีชื่อ String.chars () วิธีการคืนค่า ints ดังนั้นคอลเลคชั่นจริงจะเป็น: [100, 111, 103, 99, 97, 116] , ที่ไหน100คือรหัสของ'd', 111คือรหัสของ'o'อีกครั้ง, เพื่อจุดประสงค์ในการอธิบาย, มันถูกแสดงเป็น [d, o, g, c, a, t]


3
คำตอบที่ดีที่สุด ตรงไปยังจุดที่มีตัวอย่าง
GabrielBB

1

คำตอบง่ายๆ

mapการดำเนินงานที่สามารถผลิตStreamของStream.EXStream<Stream<Integer>>

flatMapการดำเนินการจะผลิตStreamของบางอย่างเท่านั้น EXStream<Integer>


0

การเปรียบเทียบที่ดียังสามารถอยู่กับ C # หากคุณคุ้นเคย โดยทั่วไป C # Selectคล้ายกับ Java mapและ C # JavaSelectMany flatMapเช่นเดียวกับ Kotlin สำหรับคอลเลกชัน


0

นี่เป็นสิ่งที่สับสนสำหรับผู้เริ่มต้น ความแตกต่างพื้นฐานคือmapปล่อยหนึ่งรายการสำหรับแต่ละรายการในรายการและflatMapเป็นการดำเนินการmap+ flattenเพื่อให้ชัดเจนยิ่งขึ้นให้ใช้ flatMap เมื่อคุณต้องการค่ามากกว่าหนึ่งค่าเช่นเมื่อคุณต้องการให้ loop ส่งคืนอาร์เรย์ flatMap จะมีประโยชน์มากในกรณีนี้

ฉันได้เขียนบล็อกเกี่ยวกับเรื่องนี้คุณสามารถตรวจสอบมันออกมาที่นี่



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


0

flatMap()ยังใช้ประโยชน์จากการประเมินผลลำธารขี้เกียจบางส่วน มันจะอ่านสตรีมกำปั้นและเฉพาะเมื่อจำเป็นเท่านั้นจะไปที่สตรีมถัดไป มีการอธิบายพฤติกรรมโดยละเอียดที่นี่: flatMap รับประกันว่าจะขี้เกียจหรือไม่


0

หากคุณคิดว่าmap()เป็นการวนซ้ำ (หนึ่งระดับforวนซ้ำ) flatmap()จะเป็นการวนซ้ำสองระดับ (เช่นforวนซ้ำซ้อนกัน) (ป้อนองค์ประกอบที่fooมีการทำซ้ำแต่ละรายการและทำfoo.getBarList()และทำซ้ำในองค์ประกอบนั้นbarListอีกครั้ง)


map(): รับกระแสข้อมูลทำทุกองค์ประกอบรวบรวมผลลัพธ์เดียวของทุกกระบวนการส่งออกกระแสข้อมูลอื่น คำจำกัดความของ "ทำบางสิ่งบางอย่างฟังก์ชั่น" เป็นนัย หากกระบวนการขององค์ประกอบใด ๆ ส่งผลnullให้nullใช้เพื่อเขียนสตรีมสุดท้าย ดังนั้นจำนวนองค์ประกอบในสตรีมผลลัพธ์จะเท่ากับจำนวนอินพุตสตรีม

flatmap(): รับสตรีมขององค์ประกอบ / สตรีมและฟังก์ชั่น (คำจำกัดความที่ชัดเจน) ใช้ฟังก์ชั่นกับแต่ละองค์ประกอบของแต่ละสตรีมและรวบรวมสตรีมผลกลางทั้งหมดให้เป็นสตรีมที่มากขึ้น ("แบน") หากการประมวลผลขององค์ประกอบใด ๆ ส่งผลnullให้มีการสตรีมที่ว่างเปล่าไปยังขั้นตอนสุดท้ายของ "การแบน" จำนวนองค์ประกอบในสตรีมผลลัพธ์เป็นผลรวมขององค์ประกอบที่เข้าร่วมทั้งหมดในอินพุตทั้งหมดหากอินพุตเป็นสตรีมหลายสตรีม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.