В этом посте показано, как можно использовать 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 . |