3.11 使用flatMapmap方法

问题

用户希望以某种方式转换流中的元素,但不确定该使用 map 还是 flatMap 方法。

方案

如果需要将每个元素转换为一个值,则使用 Stream.map 方法;如果需要将每个元素转换为多个值,且需要将生成的流“展平”,则使用 Stream.flatMap 方法。

讨论

mapflatMap 方法均传入 Function 作为参数。map 方法的签名如下:

  1. <R> Stream<R> map(Function<? super T,? extends R> mapper)

Function 传入一个输入,并将其转换为一个输出。map 方法则将一个 T 类型的输入转换为一个 R 类型的输出。

我们创建一个由顾客名和 Order 集合构成的 Customer 类。为简单起见,Order 类只包含一个整数 ID。例 3-54 展示了 Customer 类和 Order 类。

例 3-54 一对多关系

  1. public class Customer {
  2. private String name;
  3. private List<Order> orders = new ArrayList<>();
  4.  
  5. public Customer(String name) {
  6. this.name = name;
  7. }
  8.  
  9. public String getName() { return name; }
  10. public List<Order> getOrders() { return orders; }
  11.  
  12. public Customer addOrder(Order order) {
  13. orders.add(order);
  14. return this;
  15. }
  16. }
  17.  
  18. public class Order {
  19. private int id;
  20.  
  21. public Order(int id) {
  22. this.id = id;
  23. }
  24.  
  25. public int getId() { return id; }
  26. }

接下来,我们创建若干新顾客并添加一些订单,如例 3-55 所示。

例 3-55 客户与订单示例

  1. Customer sheridan = new Customer("Sheridan");
  2. Customer ivanova = new Customer("Ivanova");
  3. Customer garibaldi = new Customer("Garibaldi");
  4.  
  5. sheridan.addOrder(new Order(1))
  6. .addOrder(new Order(2))
  7. .addOrder(new Order(3));
  8. ivanova.addOrder(new Order(4))
  9. .addOrder(new Order(5));
  10.  
  11. List<Customer> customers = Arrays.asList(sheridan, ivanova, garibaldi);

当输入参数和输出类型之间存在一一对应的关系时,将执行 map 操作。可以将顾客映射到他们的姓名并打印,如例 3-56 所示。

例 3-56 将顾客映射到他们的姓名

  1. customers.stream()
  2. .map(Customer::getName)
  3. .forEach(System.out::println);

Stream

Stream

❸ 谢里登、伊万诺娃、加里波第

如果将顾客映射到订单而不是他们的姓名,就得到了一个集合的集合,如例 3-57 所示。

例 3-57 将顾客映射到订单

  1. customers.stream()
  2. .map(Customer::getOrders)
  3. .forEach(System.out::println);
  4. customers.stream()
  5. .map(customer -> customer.getOrders().stream())
  6. .forEach(System.out::println);

Stream>

[Order{id=1}, Order{id=2}, Order{id=3}], [Order{id=4}, Order{id=5}], []

Stream>

map 操作的结果为 Stream>,其最后一个列表为空。如果在订单列表中调用 stream 方法,则结果为 Stream>,其最后一个内部流(inner stream)为空流。

flatMap 方法的作用就在于此,其签名如下:

  1. <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

对于每个泛型参数 T,函数生成的是 Stream 而不仅仅是 R。之后,flatMap 方法从各个流中删除每个元素并将它们添加到输出,从而“展平”生成的流。

3.11 使用flatMap与map方法 - 图1 flatMap 方法的 Function 参数传入一个泛型输入参数,但生成的输出类型为 Stream

flatMap 方法的应用如例 3-58 所示。

例 3-58 对顾客订单应用 flatMap 方法

  1. customers.stream()
  2. .flatMap(customer -> customer.getOrders().stream())
  3. .forEach(System.out::println);

Stream

Stream

Order{id=1}, Order{id=2}, Order{id=3}, Order{id=4}, Order{id=5}

flatMap 操作的结果为 Stream。由于它已被“展平”,无须再担心嵌套流(nested stream)。

flatMap 方法有关的两个重要概念应予注意:

  • 方法参数 Function 产生一个输出值流;
  • 生成的元素被“展平”为一个新的流。

将上述两点谨记在心,就能体会到 flatMap 方法的有用之处。

最后需要指出的是,Java 8 引入的 Optional 类同样定义了 mapflatMap 方法,详细讨论请参见范例 6.4 和范例 6.5。

另见

有关 Optional.map 方法的讨论请参见范例 6.5,有关 Optional.flatMap 方法的讨论请参见范例 6.4。