Java 使用flatMap与map方法,用户希望以某种方式转换流中的元素,但不确定该使用 map
还是 flatMap
方法。如果需要将每个元素转换为一个值,则使用 Stream.map
方法;如果需要将每个元素转换为多个值,且需要将生成的流“展平”,则使用 Stream.flatMap
方法。
Java 使用flatMap与map方法 问题描述
用户希望以某种方式转换流中的元素,但不确定该使用 map
还是 flatMap
方法。
Java 使用flatMap与map方法 解决方案
如果需要将每个元素转换为一个值,则使用 Stream.map
方法;如果需要将每个元素转换为多个值,且需要将生成的流“展平”,则使用 Stream.flatMap
方法。
Java 使用flatMap与map方法 具体实例
map
和 flatMap
方法均传入 Function
作为参数。map
方法的签名如下:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Function
传入一个输入,并将其转换为一个输出。map
方法则将一个 T
类型的输入转换为一个 R
类型的输出。
我们创建一个由顾客名和 Order
集合构成的 Customer
类。为简单起见,Order
类只包含一个整数 ID。例 3-54 展示了 Customer
类和 Order
类。
例 3-54 一对多关系
public class Customer {
private String name;
private List<Order> orders = new ArrayList<>();
public Customer(String name) {
this.name = name;
}
public String getName() { return name; }
public List<Order> getOrders() { return orders; }
public Customer addOrder(Order order) {
orders.add(order);
return this;
}
}
public class Order {
private int id;
public Order(int id) {
this.id = id;
}
public int getId() { return id; }
}
接下来,我们创建若干新顾客并添加一些订单,如例 3-55 所示。
例 3-55 客户与订单示例
Customer sheridan = new Customer("Sheridan");
Customer ivanova = new Customer("Ivanova");
Customer garibaldi = new Customer("Garibaldi");
sheridan.addOrder(new Order(1))
.addOrder(new Order(2))
.addOrder(new Order(3));
ivanova.addOrder(new Order(4))
.addOrder(new Order(5));
List<Customer> customers = Arrays.asList(sheridan, ivanova, garibaldi);
当输入参数和输出类型之间存在一一对应的关系时,将执行 map
操作。可以将顾客映射到他们的姓名并打印,如例 3-56 所示。
例 3-56 将顾客映射到他们的姓名
customers.stream() ➊
.map(Customer::getName) ➋
.forEach(System.out::println); ➌
❶ Stream<Customer>
❷ Stream<String>
❸ 谢里登、伊万诺娃、加里波第
如果将顾客映射到订单而不是他们的姓名,就得到了一个集合的集合,如例 3-57 所示。
例 3-57 将顾客映射到订单
customers.stream()
.map(Customer::getOrders) ➊
.forEach(System.out::println); ➋
customers.stream()
.map(customer -> customer.getOrders().stream()) ➌
.forEach(System.out::println);
❶ Stream<List<Order>>
❷ [Order{id=1}, Order{id=2}, Order{id=3}], [Order{id=4}, Order{id=5}], []
❸ Stream<Stream<Order>>
map
操作的结果为 Stream<List<Order>>
,其最后一个列表为空。如果在订单列表中调用 stream
方法,则结果为 Stream<Stream<Order>>
,其最后一个内部流(inner stream)为空流。
flatMap
方法的作用就在于此,其签名如下:
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
对于每个泛型参数 T
,函数生成的是 Stream<R>
而不仅仅是 R
。之后,flatMap
方法从各个流中删除每个元素并将它们添加到输出,从而“展平”生成的流。
flatMap
方法的Function
参数传入一个泛型输入参数,但生成的输出类型为Stream
。
flatMap
方法的应用如例 3-58 所示。
例 3-58 对顾客订单应用
flatMap
方法
customers.stream() ➊
.flatMap(customer -> customer.getOrders().stream()) ➋
.forEach(System.out::println); ➌
❶ Stream<Customer>
❷ Stream<Order>
❸ Order{id=1}, Order{id=2}, Order{id=3}, Order{id=4}, Order{id=5}
flatMap
操作的结果为 Stream<Order>
。由于它已被“展平”,无须再担心嵌套流(nested stream)。
与 flatMap
方法有关的两个重要概念应予注意:
- 方法参数
Function
产生一个输出值流; -
生成的元素被“展平”为一个新的流。
将上述两点谨记在心,就能体会到 flatMap
方法的有用之处。
最后需要指出的是,Java 8 引入的 Optional
类同样定义了 map
和 flatMap
方法,详细讨论请参见范例Optional.flatMap与Optional.map方法和范例Optional的映射。