Sự khác biệt giữa các phương thức map () và FlatMap () trong Java 8 là gì?


706

Trong Java 8, sự khác biệt giữa Stream.map()Stream.flatMap()các phương thức là gì?


55
Các loại chữ ký kinda kể toàn bộ câu chuyện. map :: Stream T -> (T -> R) -> Stream R, flatMap :: Stream T -> (T -> Stream R) -> Stream R.
Chris Martin

98
fwiw, những chữ ký kiểu này thậm chí không giống Java. (Tôi biết, tôi biết - nhưng để nói rằng nó nói "toàn bộ câu chuyện" bản đồ / bản đồ phẳng giả định rất nhiều kiến ​​thức về "Java ++" mới và được cải tiến)
michael

16
@michael Chữ ký loại đó trông giống như Haskell, không phải Java. Nhưng không rõ liệu chữ ký Java thực tế có dễ đọc hơn không : <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper).
Stuart Marks

8
Ha, yeah, tôi đã đề cập đến "Java thực tế". Giống như C ++, Java hiện đại gần như không thể nhận ra đối với bất kỳ ai bắt đầu sử dụng nó vào những năm 90 (như tôi đã làm, cả hai ngôn ngữ). Chỉ cần trả lời bình luận, chữ ký của phương thức đó hầu như không nói lên "toàn bộ câu chuyện", ít nhất là không còn nữa, không phải không có giải trình bổ sung (hoặc trong trường hợp bình luận viên đó, bản dịch).
michael

2
Đó là để nói, một map's mapper lambda lợi nhuận R, một flatMap' s mapper lambda lợi nhuận một Streamcủa R( Stream<R>). Các luồng được trả về bởi trình ánh flatMapxạ được nối một cách hiệu quả. Nếu không, cả hai mapflatMaptrở về Stream<R>; sự khác biệt là những gì các lambdas mapper trở lại, Rso với Stream<R>.
derekm

Câu trả lời:


814

Cả hai mapflatMapcó thể được áp dụng cho a Stream<T>và cả hai đều trả về a Stream<R>. Sự khác biệt là maphoạt động tạo ra một giá trị đầu ra cho mỗi giá trị đầu vào, trong khi flatMaphoạt động tạo ra một giá trị số tùy ý (không hoặc nhiều hơn) cho mỗi giá trị đầu vào.

Điều này được phản ánh trong các đối số cho mỗi hoạt động.

Các maphoạt động mất Function, mà được gọi là cho mỗi giá trị trong dòng đầu vào và tạo ra một giá trị kết quả, mà sẽ được gửi đến dòng đầu ra.

Các flatMaphoạt động diễn một hàm khái niệm muốn tiêu thụ một giá trị và sản xuất một số tùy ý các giá trị. Tuy nhiên, trong Java, phương thức trả về số lượng giá trị tùy ý, vì các phương thức chỉ có thể trả về 0 hoặc một giá trị. Người ta có thể tưởng tượng một API trong đó hàm ánh xạ flatMaplấy một giá trị và trả về một mảng hoặc mộtList các giá trị, sau đó được gửi đến đầu ra. Cho rằng đây là thư viện luồng, một cách đặc biệt thích hợp để biểu thị số lượng giá trị trả về tùy ý là cho chính hàm ánh xạ trả về luồng! Các giá trị từ luồng được trả về bởi trình ánh xạ được thoát khỏi luồng và được truyền đến luồng đầu ra. "Các cụm" của các giá trị được trả về bởi mỗi lệnh gọi đến hàm ánh xạ hoàn toàn không được phân biệt trong luồng đầu ra, do đó đầu ra được cho là đã được "làm phẳng.

Việc sử dụng thông thường là cho hàm ánh xạ flatMaptrả về Stream.empty()nếu nó muốn gửi các giá trị 0 hoặc một cái gì đó giống như Stream.of(a, b, c)nếu nó muốn trả về một vài giá trị. Nhưng tất nhiên bất kỳ luồng nào có thể được trả lại.


26
Âm thanh với tôi như flatMaphoạt động là trái ngược hoàn toàn với căn hộ. Tuy nhiên, một lần nữa, hãy để nó cho các nhà khoa học máy tính để biến một thuật ngữ trên đầu của nó. Giống như một chức năng "trong suốt" có nghĩa là bạn không thể thấy bất cứ điều gì nó làm, chỉ là kết quả, trong khi nói thông thường bạn muốn một quy trình được minh bạch có nghĩa là bạn muốn mọi phần của nó được nhìn thấy.
coladict

45
@coladict Hãy thử xem nó từ một góc độ khác: đó không phải là một trường hợp trong suốt mà bạn có thể thấy các hoạt động bên trong thông qua, nhưng toàn bộ chức năng là trong suốt, tức là vô hình, đối với bạn - trong khi vẫn thực hiện công việc của họ và cho bạn thấy những gì bạn ' đang làm việc với. Trong trường hợp này, "phẳng" dùng để chỉ đối diện với "lồng", sơ đồ phẳng loại bỏ một mức lồng nhau bằng cách làm phẳng.
Zefiro

7
@coladict Điều "minh bạch" đã ăn mòn đầu tôi trong nhiều năm. Vui mừng khi biết ít nhất một người khác cũng cảm thấy như vậy.
Ashok Bijoy Debnath

9
Làm phẳng xuất phát từ việc biến cấu trúc 2 cấp thành cấu trúc một cấp, xem câu trả lời của Dici để biết ví dụ stackoverflow.com/a/26684582/6012102
andrzej.szmukala

26
Đây là lời giải thích tốt nhất về FlatMap . Đây là những gì làm cho tất cả được nhấp vào: Các giá trị từ luồng được trình ánh xạ trả về được thoát khỏi luồng và được chuyển đến luồng đầu ra. "Các cụm" của các giá trị được trả về bởi mỗi lệnh gọi đến hàm ánh xạ hoàn toàn không được phân biệt trong luồng đầu ra, do đó đầu ra được cho là đã được "làm phẳng" . Cảm ơn bạn!
neevek

464

Stream.flatMap, như có thể đoán được bằng tên của nó, là sự kết hợp giữa a mapvà một flatphép toán. Điều đó có nghĩa là trước tiên bạn áp dụng một hàm cho các phần tử của mình và sau đó làm phẳng nó. Stream.mapchỉ áp dụng một chức năng cho luồng mà không làm phẳng luồng.

Để hiểu những gì làm phẳng một luồng bao gồm , hãy xem xét một cấu trúc giống như [ [1,2,3],[4,5,6],[7,8,9] ]có "hai cấp độ". Làm phẳng điều này có nghĩa là chuyển đổi nó trong cấu trúc "một cấp" : [ 1,2,3,4,5,6,7,8,9 ].


5
đơn giản và ngọt ngào
bluelurker

3
Haha, công bằng mà nói tôi vẫn ngạc nhiên khi thấy câu hỏi này có bao nhiêu lưu lượng truy cập. Một quan sát hài hước khác là đã gần 5 năm tôi viết câu trả lời này và đã có một mô hình nâng cao khá nhất quán trong đó câu trả lời được chấp nhận nhận được khoảng hai câu trả lời cho mỗi câu trả lời của tôi. Nó đáng ngạc nhiên phù hợp.
Dici

1
Làm thế nào đây không phải là câu trả lời được chấp nhận, cảm ơn vì đã đi thẳng vào vấn đề và đưa ra một ví dụ rất đơn giản
1mike12

233

Tôi muốn đưa ra 2 ví dụ để có quan điểm thực tế hơn :
Ví dụ đầu tiên sử dụng bản đồ:

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

Không có gì đặc biệt trong ví dụ đầu tiên, một Function được áp dụng để trả vềString chữ hoa.

Ví dụ thứ hai sử dụng 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);
}

Trong ví dụ thứ hai, một luồng danh sách được thông qua. Nó KHÔNG phải là một dòng của Integer!
Nếu một hàm biến đổi phải được sử dụng (thông qua bản đồ), thì trước tiên, Luồng phải được làm phẳng thành một thứ khác (Luồng số nguyên).
Nếu FlatMap bị loại bỏ thì lỗi sau được trả về: Toán tử + không được xác định cho (các) loại đối số Danh sách, int.
KHÔNG thể áp dụng +1 trên Danh sách các số nguyên!


@PrashanthDebbadwar Tôi nghĩ rằng bạn sẽ kết thúc với một luồng Stream<Integer>chứ không phải là một luồng Integer.
payne

166

Vui lòng đi qua bài viết đầy đủ để có được một ý tưởng rõ ràng,

bản đồ so với bản đồ phẳng:

Để trả về độ dài của mỗi từ trong danh sách, chúng tôi sẽ làm một cái gì đó như dưới đây ..

Phiên bản ngắn được đưa ra dưới đây

Khi chúng tôi thu thập hai danh sách, được đưa ra dưới đây

Không có bản đồ phẳng => [1,2], [1,1] => [[1,2], [1,1]] Ở đây hai danh sách được đặt trong danh sách, do đó, đầu ra sẽ là danh sách chứa danh sách

Với bản đồ phẳng => [1,2], [1,1] => [1,2,1,1] Ở đây hai danh sách được làm phẳng và chỉ các giá trị được đặt trong danh sách, do đó, đầu ra sẽ là danh sách chỉ chứa các phần tử

Về cơ bản, nó hợp nhất tất cả các đối tượng thành một

## Phiên bản chi tiết đã được đưa ra dưới đây: -

Ví dụ: -
Xem xét một danh sách [ “STACK”, “OOOVVVER”] và chúng tôi đang cố gắng để trả về một danh sách như thế [ ‘STACKOVER’] (trở về chữ chỉ độc đáo từ danh sách đó) Ban đầu, chúng tôi sẽ làm một cái gì đó giống như bên cạnh để quay một danh sách [ “STACKOVER”] từ [ “STACK”, “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());
  }
}

Vấn đề là ở chỗ, Lambda chuyển đến phương thức bản đồ trả về một mảng Chuỗi cho mỗi từ, do đó, luồng được trả về bởi phương thức bản đồ thực sự là kiểu Stream, nhưng cái chúng ta cần là Stream để biểu diễn một luồng ký tự, bên dưới hình ảnh minh họa vấn đề.

Hình A:

nhập mô tả hình ảnh ở đây

Bạn có thể nghĩ rằng, Chúng tôi có thể giải quyết vấn đề này bằng cách sử dụng sơ đồ phẳng,
OK, hãy cho chúng tôi xem cách giải quyết vấn đề này bằng cách sử dụng bản đồArrays.flow Trước hết, bạn sẽ cần một luồng ký tự thay vì một mảng các mảng. Có một phương thức gọi là Arrays.stream () sẽ lấy một mảng và tạo ra một luồng, ví dụ:

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

Ở trên vẫn không hoạt động, vì bây giờ chúng tôi kết thúc với một danh sách các luồng (chính xác hơn là Luồng>), thay vào đó, trước tiên chúng tôi phải chuyển đổi từng từ thành một mảng các chữ cái riêng lẻ và sau đó tạo từng mảng thành một luồng riêng biệt

Bằng cách sử dụng FlatMap, chúng tôi sẽ có thể khắc phục vấn đề này như dưới đây:

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 sẽ thực hiện ánh xạ từng mảng không phải với luồng mà với nội dung của luồng đó. Tất cả các luồng riêng lẻ sẽ được tạo trong khi sử dụng bản đồ (Arrays :: stream) được hợp nhất thành một luồng duy nhất. Hình B minh họa hiệu quả của việc sử dụng phương thức FlatMap. So sánh nó với những gì bản đồ làm trong hình A. Hình B nhập mô tả hình ảnh ở đây

Phương thức FlatMap cho phép bạn thay thế từng giá trị của một luồng bằng một luồng khác và sau đó nối tất cả các luồng được tạo thành một luồng duy nhất.


2
Sơ đồ giải thích tốt đẹp.
Hitesh

107

Câu trả lời một dòng: flatMapgiúp làm phẳng a Collection<Collection<T>>thành aCollection<T> . Trong cùng một cách, nó cũng sẽ san bằng một Optional<Optional<T>>thành Optional<T>.

nhập mô tả hình ảnh ở đây

Như bạn có thể thấy, map()chỉ với :

  • Loại trung gian là Stream<List<Item>>
  • Kiểu trả về là List<List<Item>>

và với flatMap():

  • Loại trung gian là Stream<Item>
  • Kiểu trả về là List<Item>

Đây là kết quả thử nghiệm từ mã được sử dụng ngay bên dưới:

-------- Without flatMap() -------------------------------
     collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]

-------- With flatMap() ----------------------------------
     collect() returns: [Laptop, Phone, Mouse, Keyboard]

Mã được sử dụng :

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
Ví dụ rất rõ ràng .., sẽ không mất nhiều hơn vài giây để hiểu khái niệm với ví dụ của bạn ...
TechDog

2
giải thích tốt đẹp. thực sự đánh giá cao cho lời giải thích đơn giản và tốt nhất
Sachin Rane

42

Hàm bạn truyền đến stream.map phải trả về một đối tượng. Điều đó có nghĩa là mỗi đối tượng trong luồng đầu vào dẫn đến chính xác một đối tượng trong luồng đầu ra.

Hàm bạn truyền để stream.flatMaptrả về một luồng cho mỗi đối tượng. Điều đó có nghĩa là hàm có thể trả về bất kỳ số lượng đối tượng nào cho mỗi đối tượng đầu vào (bao gồm cả không có đối tượng). Các luồng kết quả sau đó được nối với một luồng đầu ra.


Tại sao bạn muốn "trả lại bất kỳ số lượng đối tượng cho mỗi đối tượng đầu vào (bao gồm không có đối tượng)"?
Derek Mahar

4
@DerekMahar Sẽ có nhiều trường hợp sử dụng cho việc này. Ví dụ: giả sử bạn có một luồng Departments trong tổ chức của mình. Mỗi bộ phận có từ 0 đến n Employees. Những gì bạn cần là một dòng của tất cả các nhân viên. Vậy bạn làm gì? Bạn viết một phương thức FlatMap lấy một bộ phận và trả về một luồng nhân viên của nó.
Philipp

Philipp, ví dụ của bạn minh họa lý do chính để sử dụng flatMap? Tôi nghi ngờ rằng nó có thể là ngẫu nhiên và không minh họa trường hợp sử dụng chính hoặc lý do tại sao flatMaptồn tại. (Tiếp tục bên dưới ...)
Derek Mahar

Sau khi đọc dzone.com/articles/under Hiểu-flatmap , tôi nghĩ động lực chính đằng sau flatMaplà điều chỉnh các lỗi sẽ xuất hiện khi sử dụng map. Làm thế nào để bạn xử lý các trường hợp trong đó một hoặc nhiều mục trong bộ gốc không thể được ánh xạ tới một mục đầu ra? Bằng cách giới thiệu một bộ trung gian (giả sử Optionalhoặc Stream) cho từng đối tượng đầu vào, flatMapcho phép bạn loại trừ các đối tượng đầu vào "không hợp lệ" (hay còn gọi là "táo xấu" theo tinh thần của stackoverflow.com/a/52248643/107158 ) khỏi bộ cuối cùng.
Derek Mahar

1
@DerekMahar Có, các điều kiện trong đó mỗi đối tượng đầu vào có thể hoặc không thể trả về một đối tượng đầu ra là một trường hợp sử dụng tốt khác cho bản đồ phẳng.
Philipp

29

đối với Bản đồ, chúng tôi có một danh sách các yếu tố và (chức năng, hành động) vì vậy:

[a,b,c] f(x) => [f(a),f(b),f(c)]

và đối với bản đồ phẳng, chúng ta có một danh sách các thành phần danh sách và chúng ta có một (hàm, hành động) f và chúng ta muốn kết quả được làm phẳng:

[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]

25

Tôi có cảm giác rằng hầu hết các câu trả lời ở đây đều khắc phục được vấn đề đơn giản. Nếu bạn đã hiểu làm thế nào các mapcông việc nên khá dễ nắm bắt.

Có những trường hợp chúng ta có thể kết thúc với các cấu trúc lồng nhau không mong muốn khi sử dụng map(), flatMap()phương pháp được thiết kế để khắc phục điều này bằng cách tránh gói.


Ví dụ:

1

List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .collect(Collectors.toList());

Chúng ta có thể tránh việc có các danh sách lồng nhau bằng cách sử dụng 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));

Ở đâu:

private Optional<String> findById(Integer id)

xin lỗi nhưng đoạn mã thứ 2 từ điểm 1 không được biên dịch thay vì List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(i -> i) .collect(Collectors.toList());. Nó nên làStream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(List::stream) .collect(Collectors.toList());
arthur

@arthur Tôi nghĩ rằng tôi đã sử dụng Stream và List của Vavr ở đây - nhưng tôi đồng ý rằng nó có thể gây nhầm lẫn một chút - Tôi sẽ thay đổi điều đó thành Java tiêu chuẩn
Grzegorz Piwowarek


22

Bài viết của Oracle về Tùy chọn nêu bật sự khác biệt này giữa bản đồ và bản đồ phẳng:

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

Thật không may, mã này không biên dịch. Tại sao? Máy tính biến là loại Optional<Computer>, vì vậy việc gọi phương thức bản đồ là hoàn toàn chính xác. Tuy nhiên, getSoundcard () trả về một đối tượng loại Tùy chọn. Điều này có nghĩa là kết quả của hoạt động bản đồ là một đối tượng của loạiOptional<Optional<Soundcard>> . Kết quả là, lệnh gọi getUSB () không hợp lệ vì Tùy chọn ngoài cùng chứa giá trị của nó là Tùy chọn khác, tất nhiên không hỗ trợ phương thức getUSB ().

Với các luồng, phương thức FlatMap lấy một hàm làm đối số, trả về một luồng khác. Hàm này được áp dụng cho từng thành phần của luồng, dẫn đến luồng phát. Tuy nhiên, FlatMap có tác dụng thay thế từng luồng được tạo bằng nội dung của luồng đó. Nói cách khác, tất cả các luồng riêng biệt được tạo bởi hàm sẽ được hợp nhất hoặc "làm phẳng" thành một luồng duy nhất. Những gì chúng tôi muốn ở đây là một cái gì đó tương tự, nhưng chúng tôi muốn "san phẳng" một Tùy chọn hai cấp thành một .

Tùy chọn cũng hỗ trợ một phương pháp FlatMap. Mục đích của nó là áp dụng hàm biến đổi trên giá trị của Tùy chọn (giống như thao tác bản đồ) và sau đó làm phẳng Tùy chọn hai cấp kết quả thành một tùy chọn duy nhất .

Vì vậy, để làm cho mã của chúng tôi chính xác, chúng ta cần viết lại nó như sau bằng cách sử dụng FlatMap:

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

FlatMap đầu tiên đảm bảo rằng một Optional<Soundcard>được trả về thay vì một Optional<Optional<Soundcard>>, và FlatMap thứ hai đạt được cùng một mục đích để trả về một Optional<USB>. Lưu ý rằng cuộc gọi thứ ba chỉ cần là map () vì getVersion () trả về một Chuỗi chứ không phải là một đối tượng Tùy chọn.

http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html


1
câu hỏi là về Stream.map và Stream.flatMap chứ không phải về Options.map anfd
Options.flatMap

4
Nhưng nó đã giúp tôi rất nhiều để hiểu vấn đề của tôi với tùy chọn và sơ đồ phẳng, cảm ơn rất nhiều!
Loïc

2
@djames, đó là một câu trả lời hoàn toàn hợp lệ, hãy đọc nó bắt đầu từ đoạn "Với các luồng, phương thức FlatMap có chức năng như một đối số ..." :)
skwisgaar

Tôi nghĩ rằng đây là một bổ sung rất hữu ích cho một số câu trả lời khác ở đây.
Mz A

Phiên bản FlatMap () cũng ném nullpulumexception nếu soundCard là null. Vậy đâu là lợi ích hứa hẹn của Tùy chọn?
Ekaterina

16

Tôi không chắc chắn tôi phải trả lời điều này, nhưng mỗi khi tôi đối mặt với một người không hiểu điều này, tôi lại sử dụng ví dụ tương tự.

Hãy tưởng tượng bạn có một quả táo. A mapđang chuyển đổi quả táo đó thành apple-juiceví dụ hoặc ánh xạ một-một .

Lấy cùng một quả táo và chỉ lấy hạt ra khỏi nó, đó là những gì flatMaplàm, hoặc một đến nhiều , một quả táo làm đầu vào, nhiều hạt làm đầu ra.


4
Đó là một ví dụ thú vị :)
cassiomolin

Đối với flatMaptrường hợp, trước tiên bạn có thu thập hạt giống từ mỗi quả táo trong các túi riêng biệt, một túi cho mỗi quả táo, trước khi bạn đổ tất cả các túi vào một túi không?
Derek Mahar

@DerekMahar nó từng là một người nghèo trong một túi duy nhất trước java-10, có nghĩa flatmaplà không thực sự lười biếng, nhưng vì java-10 nên nó lười biếng
Eugene

@Eugene hãy giải thích một khái niệm lười biếng hơn một chút mà bạn đang cố gắng giải thích không rõ ràng với tôi. Tôi hiểu những gì derkerMahar giải thích trong nhận xét đó có phải là những gì xảy ra trước java10?
JAVA

@JAVA chỉ cần tìm kiếm flatMap + lazy, tôi cá là sẽ có vài câu trả lời.
Eugene

16

map () và FlatMap ()

  1. map()

Chỉ cần lấy Hàm một lambda param trong đó T là phần tử và R phần tử trả về được xây dựng bằng T. Cuối cùng, chúng ta sẽ có Luồng với các đối tượng Loại R. Một ví dụ đơn giản có thể là:

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

Nó chỉ đơn giản là lấy các phần tử từ 1 đến 5 của Loại Integer, sử dụng từng phần tử để xây dựng một phần tử mới từ loại Stringcó giá trị "prefix_"+integer_valuevà in ra.

  1. flatMap()

Thật hữu ích khi biết rằng FlatMap () có một hàm F<T, R>trong đó

  • T là một loại từ đó một luồng có thể được xây dựng từ / với . Nó có thể là một Danh sách (T.stream ()), một mảng (Arrays.stream (someArray)), v.v. bất cứ điều gì mà Stream có thể với / hoặc biểu mẫu. trong ví dụ dưới đây mỗi dev có nhiều ngôn ngữ, vì vậy dev. Ngôn ngữ là một Danh sách và sẽ sử dụng tham số lambda.

  • R là Luồng kết quả sẽ được xây dựng bằng T. Biết rằng chúng tôi có nhiều phiên bản của T, chúng tôi sẽ tự nhiên có nhiều Luồng từ R. Tất cả các Luồng từ Loại R này sẽ được kết hợp thành một Luồng 'phẳng' duy nhất từ ​​Loại R .

Thí dụ

Các ví dụ của Bachiri Taoufiq thấy câu trả lời của nó ở đây rất đơn giản và dễ hiểu. Để rõ ràng, hãy nói rằng chúng tôi có một nhóm các nhà phát triển:

dev_team = {dev_1,dev_2,dev_3}

, với mỗi nhà phát triển biết nhiều ngôn ngữ:

dev_1 = {lang_a,lang_b,lang_c},
dev_2 = {lang_d},
dev_2 = {lang_e,lang_f}

Áp dụng Stream.map () trên dev_team để lấy ngôn ngữ của từng nhà phát triển:

dev_team.map(dev -> dev.getLanguages())

sẽ cung cấp cho bạn cấu trúc này:

{ 
  {lang_a,lang_b,lang_c},
  {lang_d},
  {lang_e,lang_f}
}

mà về cơ bản là a List<List<Languages>> /Object[Languages[]]. Không đẹp lắm, cũng không giống Java8 !!

với Stream.flatMap()bạn có thể 'làm phẳng' mọi thứ khi nó lấy cấu trúc bên trên
và biến nó thành {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}, về cơ bản có thể được sử dụng như List<Languages>/Language[]/etc...

Vì vậy, cuối cùng, mã của bạn sẽ có ý nghĩa hơn như thế này:

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

hoặc đơn giản:

dev_team
       .stream()    /* {dev_1,dev_2,dev_3} */
       .flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
       .doWhateverWithYourNewStreamHere();

Khi nào nên sử dụng map () và sử dụng FlatMap () :

  • Sử dụng map()khi mỗi phần tử kiểu T từ luồng của mình có nghĩa vụ phải được ánh xạ / chuyển thành một đơn nguyên tố của loại R. Kết quả là một bản đồ của kiểu (1 khởi đầu yếu tố -> 1 phần tử cuối) và dòng mới của các yếu tố của loại R Được trả lại.

  • Sử dụng flatMap()khi mỗi phần tử loại T từ luồng của bạn được ánh xạ / chuyển đổi thành Tập hợp các phần tử loại R. Kết quả là ánh xạ loại (1 phần tử bắt đầu -> n phần tử kết thúc) . Các Bộ sưu tập này sau đó được hợp nhất (hoặc làm phẳng ) thành một luồng các phần tử mới thuộc loại R. Ví dụ này rất hữu ích để biểu diễn các vòng lặp lồng nhau .

Trước Java 8:

List<Foo> myFoos = new ArrayList<Foo>();
    for(Foo foo: myFoos){
        for(Bar bar:  foo.getMyBars()){
            System.out.println(bar.getMyName());
        }
    }

Đăng Java 8

myFoos
    .stream()
    .flatMap(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));

11

Bản đồ: - Phương thức này lấy một Hàm làm đối số và trả về một luồng mới bao gồm các kết quả được tạo bằng cách áp dụng hàm được truyền cho tất cả các phần tử của luồng.

Hãy tưởng tượng, tôi có một danh sách các giá trị nguyên (1,2,3,4,5) và một giao diện hàm có logic là bình phương của số nguyên đã qua. (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);

đầu ra: -

[1, 4, 9, 16, 25]

Như bạn có thể thấy, một đầu ra là một luồng mới có các giá trị là bình phương của các giá trị của luồng đầu vào.

[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://codingestine.com/java-8-stream-map-method/

FlatMap: - Phương thức này lấy một Hàm làm đối số, hàm này chấp nhận một tham số T làm đối số đầu vào và trả về một luồng tham số R làm giá trị trả về. Khi chức năng này được áp dụng cho từng thành phần của luồng này, nó sẽ tạo ra một luồng các giá trị mới. Tất cả các phần tử của các luồng mới này được tạo bởi mỗi phần tử sau đó được sao chép sang luồng mới, đây sẽ là giá trị trả về của phương thức này.

Hình ảnh của tôi, tôi có một danh sách các đối tượng sinh viên, nơi mỗi sinh viên có thể chọn nhiều môn học.

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

đầu ra: -

[economics, biology, geography, science, history, math]

Như bạn có thể thấy, một đầu ra là một luồng mới có các giá trị là một tập hợp tất cả các phần tử của các luồng được trả về bởi mỗi phần tử của luồng đầu vào.

[S1, S2, S3] -> [{"history", "math", "geography"}, {"economics", "biology"}, {"khoa học", "math"}] -> lấy các môn học độc đáo - > [kinh tế, sinh học, địa lý, khoa học, lịch sử, toán học]

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


có thể tạo sự khác biệt nếu bạn cung cấp mã thay vì chỉ kích hoạt liên kết doc
Charles-Antoine Fournel

11

.map dành cho ánh xạ A -> B

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

nó chuyển đổi bất kỳ mục Anào thành bất kỳ mục nào B. Javadoc


.flatMap dành cho A -> Luồng <B> concatinating

Stream.of("dog", "cat")             // stream of 2 Strings
    .flatMapToInt(s -> s.chars())   // stream of 6 ints:      [d, o, g, c, a, t]

nó --1 chuyển đổi bất kỳ mục nào Athành Stream< B>, sau đó --2 nối tất cả các luồng thành một luồng (phẳng). Javadoc


Lưu ý 1: Mặc dù ví dụ sau căn hộ một luồng nguyên thủy (IntStream) thay vì luồng đối tượng (Luồng), nó vẫn minh họa ý tưởng của .flatMap.

Lưu ý 2: Mặc dù tên, phương thức String.chars () trả về ints. Vì vậy, bộ sưu tập thực tế sẽ là : [100, 111, 103, 99, 97, 116] , 100mã của'd' , 111là mã của 'o'v.v ... Một lần nữa, với mục đích minh họa, nó được trình bày dưới dạng [d, o, g, c, a, t].


3
Câu trả lời tốt nhất. Đi thẳng vào vấn đề với các ví dụ
GabrielBB

1

Câu trả lời đơn giản.

Các maphoạt động có thể tạo ra một Streamsố Stream.EXStream<Stream<Integer>>

flatMaphoạt động sẽ chỉ sản xuất Streammột cái gì đó. VÍ DỤStream<Integer>


0

Tương tự tốt cũng có thể với C # nếu bạn quen thuộc. Về cơ bản C # Selecttương tự java mapvà C # SelectManyjava flatMap. Áp dụng tương tự cho Kotlin cho các bộ sưu tập.


0

Điều này rất khó hiểu cho người mới bắt đầu. Sự khác biệt cơ bản là mapphát ra một mục cho mỗi mục trong danh sách và flatMapvề cơ bản là map+flatten thao tác . Để rõ ràng hơn, hãy sử dụng FlatMap khi bạn yêu cầu nhiều hơn một giá trị, ví dụ như khi bạn đang mong đợi một vòng lặp trả về mảng, FlatMap sẽ thực sự hữu ích trong trường hợp này.

Tôi đã viết một blog về điều này, bạn có thể kiểm tra nó ở đây .



0

Luồng hoạt động flatMapmapchấp nhận một chức năng như đầu vào.

flatMapmong muốn hàm trả về một luồng mới cho mỗi phần tử của luồng và trả về một luồng kết hợp tất cả các phần tử của luồng được trả về bởi hàm cho mỗi phần tử. Nói cách khác, với flatMap, đối với mỗi phần tử từ nguồn, nhiều phần tử sẽ được tạo bởi hàm. http://www.zoftino.com/java-stream-examples#flatmap-operation

mapmong muốn hàm trả về một giá trị được chuyển đổi và trả về một luồng mới chứa các phần tử được chuyển đổi. Nói cách khác, với map, đối với mỗi phần tử từ nguồn, một phần tử được chuyển đổi sẽ được tạo bởi hàm. http://www.zoftino.com/java-stream-examples#map-operation


0

flatMap()cũng tận dụng đánh giá lười biếng một phần của các luồng. Nó sẽ đọc luồng nắm tay và chỉ khi được yêu cầu, sẽ chuyển sang luồng tiếp theo. Hành vi được giải thích chi tiết ở đây: FlatMap có đảm bảo lười biếng không?


0

Nếu bạn nghĩ map()như một forvòng lặp (vòng lặp một cấp ), flatmap()là vòng lặp hai cấp độ (giống như forvòng lặp lồng nhau ). (Nhập từng phần tử lặp foo, và làm foo.getBarList()và lặp lại trong đó barListmột lần nữa)


map(): lấy một luồng, làm một cái gì đó cho mọi phần tử, thu thập kết quả duy nhất của mỗi quá trình, xuất ra một luồng khác. Định nghĩa của "làm một cái gì đó chức năng" là ẩn. Nếu quá trình xử lý bất kỳ phần tử nào dẫn đến null, nullđược sử dụng để soạn luồng cuối cùng. Vì vậy, số lượng phần tử trong luồng kết quả sẽ bằng số lượng luồng đầu vào.

flatmap(): lấy một luồng các phần tử / luồng và một hàm (định nghĩa rõ ràng), áp dụng hàm cho từng phần tử của mỗi luồng và thu thập tất cả các luồng kết quả trung gian thành một luồng lớn hơn ("làm phẳng"). Nếu quá trình xử lý bất kỳ phần tử nào dẫn đến null, luồng trống được cung cấp cho bước cuối cùng của "làm phẳng". Số lượng phần tử trong luồng kết quả, là tổng của tất cả các phần tử tham gia trong tất cả các đầu vào, nếu đầu vào là một số luồng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.