Какой функциональный интерфейс принимает метод groupingBy в Collectors?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Функциональный интерфейс в Collectors.groupingBy()
Метод groupingBy() в классе Collectors — один из самых мощных инструментов Stream API для группировки элементов. Для понимания того, как его использовать, нужно разобраться, какой функциональный интерфейс он принимает.
Основное определение
Метод Collectors.groupingBy() имеет несколько перегруженных версий. Основная сигнатура:
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
Function<? super T, ? extends K> classifier
)
Ответ: Метод groupingBy() принимает функциональный интерфейс Function<T, K> в качестве первого параметра.
Function<T, K> — что это?
Function — функциональный интерфейс из пакета java.util.function, который:
- Принимает один аргумент типа T
- Возвращает значение типа K
- Имеет один абстрактный метод:
K apply(T t)
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
В контексте groupingBy(Function<? super T, ? extends K> classifier):
- T — тип элемента в потоке
- K — тип ключа (по которому группируем)
- Function преобразует каждый элемент в ключ группировки
Практические примеры
Пример 1: Группировка строк по длине
List<String> words = Arrays.asList(
"apple", "banana", "apricot", "blueberry", "cat", "dog"
);
// Группируем по длине слова (String -> Integer)
Map<Integer, List<String>> grouped = words.stream()
.collect(Collectors.groupingBy(
Function.identity() // Используем сам объект как ключ
));
// Результат:
// {
// 3: [cat, dog],
// 5: [apple],
// 6: [banana],
// 7: [apricot],
// 9: [blueberry]
// }
// Или более явно с lambda:
Map<Integer, List<String>> grouped2 = words.stream()
.collect(Collectors.groupingBy(word -> word.length()));
Пример 2: Группировка объектов
public class Person {
private String name;
private int age;
private String city;
public Person(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public int getAge() { return age; }
public String getCity() { return city; }
}
List<Person> people = Arrays.asList(
new Person("Alice", 25, "New York"),
new Person("Bob", 30, "Los Angeles"),
new Person("Charlie", 25, "New York"),
new Person("David", 30, "Chicago"),
new Person("Eve", 25, "Los Angeles")
);
// Группируем по возрасту (Person -> Integer)
Map<Integer, List<Person>> byAge = people.stream()
.collect(Collectors.groupingBy(
person -> person.getAge() // Function<Person, Integer>
));
// Результат:
// {
// 25: [Alice, Charlie, Eve],
// 30: [Bob, David]
// }
// Группируем по городу (Person -> String)
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(
person -> person.getCity() // Function<Person, String>
));
// Результат:
// {
// "New York": [Alice, Charlie],
// "Los Angeles": [Bob, Eve],
// "Chicago": [David]
// }
Перегруженные версии groupingBy()
Версия 1: Только Function
// Сигнатура:
// <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
// Function<? super T, ? extends K> classifier
// )
Map<Integer, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
// Результат: Map<Integer, List<Person>>
Версия 2: Function + Collector (для кастомной агрегации)
// Сигнатура:
// <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(
// Function<? super T, ? extends K> classifier,
// Collector<? super T, A, D> downstream
// )
// Группируем по возрасту и считаем количество людей в каждой группе
Map<Integer, Long> countByAge = people.stream()
.collect(Collectors.groupingBy(
Person::getAge, // Function<Person, Integer>
Collectors.counting() // Collector<Person, ?, Long>
));
// Результат:
// {
// 25: 3,
// 30: 2
// }
// Группируем по городу и собираем имена в строку
Map<String, String> namesByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity, // Function<Person, String>
Collectors.mapping(
Person::getName, // Function<Person, String>
Collectors.joining(", ") // Collector<String, ?, String>
)
));
// Результат:
// {
// "New York": "Alice, Charlie",
// "Los Angeles": "Bob, Eve",
// "Chicago": "David"
// }
Версия 3: Function + Map Factory + Collector
// Сигнатура:
// <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(
// Function<? super T, ? extends K> classifier,
// Supplier<M> mapFactory,
// Collector<? super T, A, D> downstream
// )
// Группируем в LinkedHashMap (сохраняет порядок)
Map<Integer, Long> ordered = people.stream()
.collect(Collectors.groupingBy(
Person::getAge, // Function<Person, Integer>
LinkedHashMap::new, // Supplier<LinkedHashMap>
Collectors.counting() // Collector<Person, ?, Long>
));
Сложные примеры
Пример: Многоуровневая группировка
// Группируем сначала по городу, потом по возрасту
Map<String, Map<Integer, List<Person>>> complex = people.stream()
.collect(Collectors.groupingBy(
Person::getCity, // Первый Function
Collectors.groupingBy(
Person::getAge // Второй Function
)
));
// Результат:
// {
// "New York": {
// 25: [Alice, Charlie],
// 30: []
// },
// "Los Angeles": {
// 25: [Eve],
// 30: [Bob]
// },
// "Chicago": {
// 30: [David]
// }
// }
Пример: Агрегация с Function
// Группируем по возрасту и находим сумму (но для объектов)
Map<Integer, Integer> sumByAge = people.stream()
.collect(Collectors.groupingBy(
Person::getAge,
Collectors.summingInt(p -> 1) // Подсчитываем элементы
));
// Или более практично - группируем и находим максимум
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Map<Integer, Integer> maxByMod = numbers.stream()
.collect(Collectors.groupingBy(
n -> n % 3, // Function<Integer, Integer>
Collectors.maxBy(Integer::compare) // Optional<Integer>
));
// Результат:
// {
// 0: Optional[9],
// 1: Optional[7],
// 2: Optional[8]
// }
Эквивалентное решение без Collectors
// Что делает groupingBy внутри
private static <T, K> Map<K, List<T>> groupBy(
List<T> items,
Function<T, K> classifier
) {
Map<K, List<T>> result = new HashMap<>();
for (T item : items) {
K key = classifier.apply(item); // Применяем Function
result.computeIfAbsent(key, k -> new ArrayList<>())
.add(item);
}
return result;
}
Типичные ошибки
Ошибка 1: Забыли вернуть ключ
// НЕПРАВИЛЬНО
Map<Integer, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(p -> {
System.out.println(p.getName()); // Побочный эффект
// Забыли return!
}));
// ПРАВИЛЬНО
Map<Integer, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(p -> p.getAge()));
Ошибка 2: Неправильный тип возврата Function
// НЕПРАВИЛЬНО
Map<String, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(p -> p.getAge())); // Возвращает int
// Компилятор скажет: List содержит Person, но ключ - Integer
// ПРАВИЛЬНО
Map<Integer, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(p -> p.getAge())); // Возвращает Integer
Резюме
Ответ: Метод Collectors.groupingBy() принимает функциональный интерфейс Function<T, K>, где:
- T — тип элементов в потоке
- K — тип ключа группировки
Функция должна преобразовать каждый элемент потока в ключ, по которому будет выполнена группировка.
stream.collect(
Collectors.groupingBy(
element -> extractKey(element) // Function<T, K>
)
);