Какие знаешь коллекторы в Stream API?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Коллекторы в Stream API
Collectors — это функциональные интерфейсы для накопления элементов потока в результирующую коллекцию или значение. Это заключительная операция (terminal operation) в Stream Pipeline.
Базовые коллекторы
1. toList() / toCollection()
Наиболее часто используемый коллектор:
public class ListCollectorExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Собрать в List (неизменяемый)
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
// Результат: [Alice, Charlie, David]
// Собрать в ArrayList
ArrayList<String> arrayList = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
// Собрать в LinkedList
LinkedList<String> linkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));
}
}
2. toSet()
Удаление дубликатов и сохранение в Set:
public class SetCollectorExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// Собрать в HashSet
Set<Integer> uniqueNumbers = numbers.stream()
.collect(Collectors.toSet());
// Результат: [1, 2, 3, 4] (порядок не гарантирован)
// Собрать в TreeSet (отсортированный)
TreeSet<Integer> sortedSet = numbers.stream()
.collect(Collectors.toCollection(TreeSet::new));
// Результат: [1, 2, 3, 4] (отсортирован)
}
}
3. toMap()
Создание Map из элементов:
public class MapCollectorExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// toMap(keyMapper, valueMapper)
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name, // ключ
String::length // значение
));
// Результат: {Alice=5, Bob=3, Charlie=7}
// toMap с mergeFunctionn (для дубликатов)
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
Map<Integer, Integer> numberToCount = numbers.stream()
.collect(Collectors.toMap(
num -> num, // ключ
num -> 1, // начальное значение
Integer::sum // как объединить дубликаты
));
// Результат: {1=1, 2=2, 3=3}
// toMap с supplier для типа Map
LinkedHashMap<String, Integer> linkedMap = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length,
(a, b) -> a,
LinkedHashMap::new
));
}
}
Агрегирующие коллекторы
4. joining()
Объединение строк в одну:
public class JoiningCollectorExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
// Простое объединение
String result = fruits.stream()
.collect(Collectors.joining());
// Результат: "applebananacherry"
// С разделителем
String csv = fruits.stream()
.collect(Collectors.joining(", "));
// Результат: "apple, banana, cherry"
// С префиксом и суффиксом
String bracketed = fruits.stream()
.collect(Collectors.joining(", ", "[", "]"));
// Результат: "[apple, banana, cherry]"
}
}
5. counting() / summingInt() / averagingDouble()
Вычисление статистики:
public class StatisticsCollectorExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
// Количество элементов
long count = numbers.stream()
.collect(Collectors.counting());
// Результат: 5
// Сумма
int sum = numbers.stream()
.collect(Collectors.summingInt(n -> n));
// Результат: 150
// Среднее
double average = numbers.stream()
.collect(Collectors.averagingInt(n -> n));
// Результат: 30.0
// Минимум и максимум
Optional<Integer> max = numbers.stream()
.collect(Collectors.maxBy(Integer::compare));
Optional<Integer> min = numbers.stream()
.collect(Collectors.minBy(Integer::compare));
// Полная статистика
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(n -> n));
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
}
}
Группирующие коллекторы
6. groupingBy()
Группирование элементов по критерию:
public class GroupingByExample {
static class Student {
String name;
int grade;
String department;
Student(String name, int grade, String department) {
this.name = name;
this.grade = grade;
this.department = department;
}
}
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 90, "CS"),
new Student("Bob", 85, "CS"),
new Student("Charlie", 92, "Math"),
new Student("David", 88, "Math")
);
// Группирование по отделению
Map<String, List<Student>> byDepartment = students.stream()
.collect(Collectors.groupingBy(s -> s.department));
// {CS=[Alice, Bob], Math=[Charlie, David]}
// Группирование с трансформацией значений
Map<String, List<String>> namesByDepartment = students.stream()
.collect(Collectors.groupingBy(
s -> s.department,
Collectors.mapping(s -> s.name, Collectors.toList())
));
// {CS=[Alice, Bob], Math=[Charlie, David]}
// Группирование с подсчётом
Map<String, Long> countByDepartment = students.stream()
.collect(Collectors.groupingBy(
s -> s.department,
Collectors.counting()
));
// {CS=2, Math=2}
// Группирование с статистикой
Map<String, IntSummaryStatistics> statsByDepartment = students.stream()
.collect(Collectors.groupingBy(
s -> s.department,
Collectors.summarizingInt(s -> s.grade)
));
}
}
7. partitioningBy()
Разделение на две группы по условию (true/false):
public class PartitioningByExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Разделить на чётные и нечётные
Map<Boolean, List<Integer>> evenOdd = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
List<Integer> evenNumbers = evenOdd.get(true);
List<Integer> oddNumbers = evenOdd.get(false);
// С трансформацией
Map<Boolean, List<String>> partition = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n > 5,
Collectors.mapping(Object::toString, Collectors.toList())
));
// {false=[1, 2, 3, 4, 5], true=[6, 7, 8, 9, 10]}
}
}
Специализированные коллекторы
8. filtering() / flatMapping()
Фильтрация и преобразование внутри collector:
public class FilteringExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Фильтрация внутри collector
List<Integer> evenNumbers = numbers.stream()
.collect(Collectors.filtering(
n -> n % 2 == 0,
Collectors.toList()
));
// Результат: [2, 4]
// flatMapping
List<String> words = Arrays.asList("hello", "world");
List<String> letters = words.stream()
.collect(Collectors.flatMapping(
word -> word.chars().mapToObj(c -> String.valueOf((char) c)),
Collectors.toList()
));
// Результат: [h, e, l, l, o, w, o, r, l, d]
}
}
9. reducing()
Уменьшение (reduction) с аккумулятором:
public class ReducingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Сумма через reducing
int sum = numbers.stream()
.collect(Collectors.reducing(
0, // начальное значение
n -> n, // mapper
Integer::sum // accumulator
));
// Результат: 15
// Произведение
int product = numbers.stream()
.collect(Collectors.reducing(
1,
n -> n,
(a, b) -> a * b
));
// Результат: 120
// Поиск максимума
Optional<Integer> max = numbers.stream()
.collect(Collectors.reducing(
(a, b) -> a > b ? a : b
));
}
}
10. teeing() — Java 12+
Разветвление на два коллектора:
public class TeeingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Одновременное вычисление суммы и средней
String result = numbers.stream()
.collect(Collectors.teeing(
Collectors.summingInt(Integer::intValue), // collector 1
Collectors.averagingInt(Integer::intValue), // collector 2
(sum, avg) -> "Sum: " + sum + ", Avg: " + avg // combiner
));
// Результат: "Sum: 15, Avg: 3.0"
}
}
Пользовательские коллекторы
public class CustomCollectorExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("hello", "world", "java");
// Пользовательский collector для подсчёта букв
Collector<String, ?, Map<Character, Integer>> characterCounter =
Collector.of(
HashMap::new, // supplier
(map, word) -> { // accumulator
for (char c : word.toCharArray()) {
map.merge(c, 1, Integer::sum);
}
},
(map1, map2) -> { // combiner
map2.forEach((k, v) -> map1.merge(k, v, Integer::sum));
return map1;
}
);
Map<Character, Integer> charCount = words.stream()
.collect(characterCounter);
// {h=1, e=1, l=3, o=2, w=1, r=1, d=1, j=1, a=1, v=1}
}
}
Сравнительная таблица
| Коллектор | Возвращает | Использование |
|---|---|---|
| toList | List | Основной случай |
| toSet | Set | Удаление дубликатов |
| toMap | Map | Создание словаря |
| joining | String | Объединение строк |
| counting | Long | Подсчёт элементов |
| summingInt | Integer | Сумма значений |
| groupingBy | Map<K, List<V>> | Группирование |
| partitioningBy | Map<Boolean, List> | Разделение на две группы |
| filtering | Результат | Фильтрация в коллекторе |
| reducing | Optional | Аккумуляция |
| teeing | Комбинированный | Два коллектора |
Best Practices
public class CollectorBestPractices {
// 1. ✅ Используй toList() для простых случаев
// 2. ✅ Используй groupingBy() для группирования
// 3. ✅ Используй mapping() для трансформации внутри collector
// 4. ✅ Используй flatMapping() для "развёртывания" потоков
// 5. ✅ Учитывай производительность с большими данными
// 6. ❌ Избегай создания промежуточных List если можно обойтись
// 7. ✅ Используй parallelStream() для больших объёмов данных
}
Вывод
Коллекторы в Stream API — это мощный инструмент для обработки потоков данных. Основные категории:
- Базовые: toList, toSet, toMap
- Агрегирующие: counting, summingInt, averaging
- Группирующие: groupingBy, partitioningBy
- Специализированные: filtering, reducing, teeing
Правильный выбор коллектора может значительно упростить код и повысить производительность.