В этом посте показано, как можно использовать Collectors доступные в Streams API, для группировки элементов потока с помощью groupingBy и элементов разделения потока с помощью partitioningBy .
Рассмотрим поток объектов Employee , каждый с именем, городом и количеством продаж, как показано в таблице ниже:
|
1
2
3
4
5
6
7
8
|
+----------+------------+-----------------+| Name | City | Number of Sales |+----------+------------+-----------------+| Alice | London | 200 || Bob | London | 150 || Charles | New York | 160 || Dorothy | Hong Kong | 190 |+----------+------------+-----------------+ |
Группировка
Давайте начнем с группировки сотрудников по городам с использованием императивного стиля (pre-lamba) Java:
|
01
02
03
04
05
06
07
08
09
10
|
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 , например так:
|
1
2
|
Map<String, List<Employee>> employeesByCity = employees.stream().collect(groupingBy(Employee::getCity)); |
Это приводит к следующей карте:
|
1
|
{New York=[Charles], Hong Kong=[Dorothy], London=[Alice, Bob]} |
Кроме того, можно подсчитать количество сотрудников в каждом городе, передав коллектор counting groupingBy . Второй коллектор выполняет дальнейшую операцию сокращения для всех элементов в потоке, классифицированных в той же группе.
|
1
2
|
Map<String, Long> numEmployeesByCity = employees.stream().collect(groupingBy(Employee::getCity, counting())); |
В результате получается следующая карта:
|
1
|
{New York=1, Hong Kong=1, London=2} |
Кроме того, это эквивалентно следующему оператору SQL:
|
1
|
select city, count(*) from Employee group by city |
Другим примером является вычисление среднего количества продаж в каждом городе, что можно сделать с помощью коллектора averagingInt в сочетании с коллектором groupingBy :
|
1
2
3
|
Map<String, Double> avgSalesByCity = employees.stream().collect(groupingBy(Employee::getCity, averagingInt(Employee::getNumSales))); |
В результате получается следующая карта:
|
1
|
{New York=160.0, Hong Kong=190.0, London=175.0} |
Разметка
Разделение — это особый вид группировки, в которой результирующая карта содержит не более двух разных групп — одну для true и одну для false . Например, если вы хотите выяснить, кто ваши лучшие сотрудники, вы можете разделить их на тех, кто сделал больше, чем N продаж, и тех, кто этого не сделал, используя сборщик partitioningBy :
|
1
2
|
Map<Boolean, List<Employee>> partitioned = employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150)); |
Это даст следующий результат:
|
1
|
{false=[Bob], true=[Alice, Charles, Dorothy]} |
Вы также можете объединить разделение и группировку, передав сборщик groupingBy сборщик partitioningBy . Например, вы можете посчитать количество сотрудников в каждом городе в каждом разделе:
|
1
2
3
|
Map<Boolean, Map<String, Long>> result = employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150, groupingBy(Employee::getCity, counting()))); |
Это создаст двухуровневую карту:
|
1
|
{false={London=1}, true={New York=1, Hong Kong=1, London=1}} |
| Ссылка: | Java 8 Streams API: группировка и разбиение потока от нашего партнера по JCG Фахда Шарифа в блоге fahd.blog . |