← Назад к вопросам

Какие знаешь коллекторы в Stream API?

1.8 Middle🔥 251 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Коллекторы в 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}
    }
}

Сравнительная таблица

КоллекторВозвращаетИспользование
toListListОсновной случай
toSetSetУдаление дубликатов
toMapMapСоздание словаря
joiningStringОбъединение строк
countingLongПодсчёт элементов
summingIntIntegerСумма значений
groupingByMap<K, List<V>>Группирование
partitioningByMap<Boolean, List>Разделение на две группы
filteringРезультатФильтрация в коллекторе
reducingOptionalАккумуляция
teeingКомбинированныйДва коллектора

Best Practices

public class CollectorBestPractices {
    // 1. ✅ Используй toList() для простых случаев
    // 2. ✅ Используй groupingBy() для группирования
    // 3. ✅ Используй mapping() для трансформации внутри collector
    // 4. ✅ Используй flatMapping() для "развёртывания" потоков
    // 5. ✅ Учитывай производительность с большими данными
    // 6. ❌ Избегай создания промежуточных List если можно обойтись
    // 7. ✅ Используй parallelStream() для больших объёмов данных
}

Вывод

Коллекторы в Stream API — это мощный инструмент для обработки потоков данных. Основные категории:

  1. Базовые: toList, toSet, toMap
  2. Агрегирующие: counting, summingInt, averaging
  3. Группирующие: groupingBy, partitioningBy
  4. Специализированные: filtering, reducing, teeing

Правильный выбор коллектора может значительно упростить код и повысить производительность.

Какие знаешь коллекторы в Stream API? | PrepBro