Written By: Lê Anh Tuấn (Java 10)
Gmail: anhtuan097856@gmail.com
Bài tham khảo: The Difference Between map() and flatMap()

1. Tổng quan

map() và flatMap() là 2 khái niệm được Java vay mượn từ functional languages. Trong Java 8, chúng ta có thể tìm thấy chúng trong Optional , Stream và CompletableFuture.
Mặc dù thực tế là cả hai đều có cùng kiểu trả về nhưng chúng khá khác nhau. Sau đây mình giải thích những khác biệt này bằng cách phân tích một số ví dụ về Optional vs Stream.

2. map() và flatMap() trong Optional

Phương thức map() sử dụng để chuyển một Optional của Object này sang Object khác có thể cùng kiểu dữ liêu.

Optional<String> s = Optional.of("test");
Optional<Integer> r = s.map(String::length);

Tuy nhiên, trong những trường hợp phức tạp hơn, chúng ta cũng có thể được cung cấp một hàm trả về Optional. Trong những trường hợp như vậy, việc sử dụng map () sẽ dẫn đến một cấu trúc lồng nhau, vì việc triển khai map () thực hiện một gói bổ sung bên trong.

Optional<Optional<String>> s = Optional.of(Optional.of("test"));

Như chúng ta thấy, chúng ta kết thúc với cấu trúc Optional lồng nhau. Mặc dù nó hoạt động nhưng nó khá cồng kềnh để sử dụng và không cung cấp thêm bất kỳ sự an toàn nào, vì vậy tốt hơn là bạn nên giữ một cấu trúc phẳng. Chúng ta cần sử dụng đến flatMap() để làm phẳng dữ liệu.

Optional<Optional<String>> s = Optional.of(Optional.of("test"));
Optional<Integer>  size  = s.flatMap(q -> q).map(String::length);

Như ví dụ trên, flatMap() để giúp chúng ta chuyển Optional lồng bên trong một Optional khác sang Optional<String> cấu trúc phẳng, từ đó chúng ta có thể sử dụng map() để có được kết quả tương tự.

3. Map() và FlatMap() trong Stream

Cả 2 method đều hoạt động như nhau trong Optional và Stream.

Sử dụng map()

List<String> myList = Stream.of("a", "b")
  .map(String::toUpperCase)
  .collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);

map () hoạt động khá tốt trong trường hợp đơn giản như vậy. Nhưng điều gì sẽ xảy ra nếu chúng ta có một thứ gì đó phức tạp hơn, chẳng hạn như danh sách các danh sách làm đầu vào? Hãy xem ví dụ sau:

List<List<String>> list = Arrays.asList(
  Arrays.asList("a"),
  Arrays.asList("b"));
System.out.println(list);

Kết quả trả về : [[a], [b]]

Bây giờ chúng ta hãy sử dụng flatMap ():

System.out.println(list
  .stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList()));

Kết quả trả về : [a, b]

Phương thức flatMap () trước tiên làm phẳng đầu vào Stream of Streams thành Stream of Strings.
Sau đó, nó hoạt động tương tự như phương thức map ().

4. Kết luận

  • Điểm khác biệt cốt lõi giữa map và flatMap là kiểu trả về (return type).

  • Phương thức Stream map() chỉ xử lí và trả về duy nhất một kết quả, kết quả này được trả về thông qua Output của stream.

  • Phương thức flatMap() thay thế các giá trị bằng Stream, KẾT HỢP các Stream lại với nhau.

    Thanks for watching!