Java 8 Streams API:對Stream分組和分割槽

ImportNew發表於2015-12-30

這篇文章展示瞭如何使用 Streams API 中的 Collector 及 groupingBy 和 partitioningBy 來對流中的元素進行分組和分割槽。

思考一下 Employee 物件流,每個物件對應一個名字、城市和銷售數量,如下表所示:

+----------+------------+-----------------+
| Name     | City       | Number of Sales |
+----------+------------+-----------------+
| Alice    | London     | 200             |
| Bob      | London     | 150             |
| Charles  | New York   | 160             |
| Dorothy  | Hong Kong  | 190             |
+----------+------------+-----------------+

分組

首先,我們利用(lambda表示式出現之前的)命令式風格Java 程式對流中的僱員按城市進行分組:

Map<String, List<Employee>> result = new HashMap<>();
for (Employee e : employees) {
  String city = e.getCity();
  List<Employee> empsInCity = result.get(city);
  if (empsInCity == null) {
    empsInCity = new ArrayList<>();
    result.put(city, empsInCity);
  }
  empsInCity.add(e);
}

你可能很熟悉寫這樣的程式碼,你也看到了,一個如此簡單的任務就需要這麼多程式碼!

而在 Java 8 中,你可以使用 groupingBy 收集器,一條語句就能完成相同的功能,像這樣:

Map<String, List<Employee>> employeesByCity =
  employees.stream().collect(groupingBy(Employee::getCity));

結果如下面的 map 所示:

{New York=[Charles], Hong Kong=[Dorothy], London=[Alice, Bob]}

還可以計算每個城市中僱員的數量,只需傳遞一個計數收集器給 groupingBy 收集器。第二個收集器的作用是在流分類的同一個組中對每個元素進行遞迴操作。

Map<String, Long> numEmployeesByCity =
  employees.stream().collect(groupingBy(Employee::getCity, counting()));

結果如下面的 map 所示:

{New York=1, Hong Kong=1, London=2}

順便提一下,該功能與下面的 SQL 語句是等同的:

select city, count(*) from Employee group by city

另一個例子是計算每個城市的平均年齡,這可以聯合使用 averagingInt 和 groupingBy 收集器:

Map<String, Double> avgSalesByCity =
  employees.stream().collect(groupingBy(Employee::getCity,
                               averagingInt(Employee::getNumSales)));

結果如下 map 所示:

{New York=160.0, Hong Kong=190.0, London=175.0}

分割槽

分割槽是一種特殊的分組,結果 map 至少包含兩個不同的分組——一個true,一個false。例如,如果想找出最優秀的員工,你可以將所有僱員分為兩組,一組銷售量大於 N,另一組小於 N,使用 partitioningBy 收集器:

Map<Boolean, List<Employee>> partitioned =
  employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150));

輸出如下結果:

{false=[Bob], true=[Alice, Charles, Dorothy]}

你也可以將 groupingBy 收集器傳遞給 partitioningBy 收集器來將聯合使用分割槽和分組。例如,你可以統計每個分割槽中的每個城市的僱員人數:

Map<Boolean, Map<String, Long>> result =
  employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150,
                               groupingBy(Employee::getCity, counting())));

這樣會生成一個二級 Map:

{false={London=1}, true={New York=1, Hong Kong=1, London=1}}

相關文章